k8s学习笔记-23-资源指标和集群监控

[TOC]

1、资源指标和资源监控

一个集群系统管理离不开监控,同样的Kubernetes也需要根据数据指标来采集相关数据,从而完成对集群系统的监控状况进行监测。这些指标总体上分为两个组成:监控集群本身和监控Pod对象,通常一个集群的衡量性指标包括以下几个部分:

  • 节点资源状态:主要包括网络带宽、磁盘空间、CPU和内存使用率
  • 节点的数量:即时性了解集群的可用节点数量可以为用户计算服务器使用的费用支出提供参考。
  • 运行的Pod对象:正在运行的Pod对象数量可以评估可用节点数量是否足够,以及节点故障时是否能平衡负载。

另一个方面,对Pod资源对象的监控需求大概有以下三类:

  • Kubernetes指标:监测特定应用程序相关的Pod对象的部署过程、副本数量、状态信息、健康状态、网络等等。
  • 容器指标:容器的资源需求、资源限制、CPU、内存、磁盘空间、网络带宽的实际占用情况。
  • 应用程序指标:应用程序自身的内建指标,和业务规则相关

2、Weave Scope监控集群

Weave Scope 是 Docker 和 Kubernetes 可视化监控工具。Scope 提供了至上而下的集群基础设施和应用的完整视图,用户可以轻松对分布式的容器化应用进行实时监控和问题诊断。 对于复杂的应用编排和依赖关系,scope可以使用清晰的图标一览应用状态和拓扑关系。

(1)Weave Scope部署

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[root@k8s-master mainfests]# kubectl apply -f "https://cloud.weave.works/k8s/scope.yaml?k8s-version=$(kubectl version | base64 | tr -d '\n')"
namespace/weave created #创建名称空间weave,也可以在创建时指定名称空间
serviceaccount/weave-scope created #创建serviceaccount
clusterrole.rbac.authorization.k8s.io/weave-scope created
clusterrolebinding.rbac.authorization.k8s.io/weave-scope created
deployment.apps/weave-scope-app created #创建deployment
service/weave-scope-app created #创建service
daemonset.extensions/weave-scope-agent created #创建deamonset
[root@k8s-master mainfests]# kubectl get ns
NAME STATUS AGE
default Active 68d
ingress-nginx Active 28d
kube-public Active 68d
kube-system Active 68d
weave Active 1m
[root@k8s-master mainfests]# kubectl get deployment -n weave
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
weave-scope-app 1 1 1 1 1m
[root@k8s-master mainfests]# kubectl get svc -n weave
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
weave-scope-app ClusterIP 10.97.229.215 <none> 80/TCP 33s
[root@k8s-master mainfests]# kubectl get pod -n weave
NAME READY STATUS RESTARTS AGE
weave-scope-agent-5876w 1/1 Running 0 1m
weave-scope-agent-d6jgt 1/1 Running 0 1m
weave-scope-agent-t9p5g 1/1 Running 0 1m
weave-scope-app-578556559-nfxrf 1/1 Running 0 1m
    1. DaemonSet weave-scope-agent,集群每个节点上都会运行的 scope agent 程序,负责收集数据。
    1. Deployment weave-scope-app,scope 应用,从 agent 获取数据,通过 Web UI 展示并与用户交互。
    1. Service weave-scope-app,默认是 ClusterIP 类型,为了方便已通过 kubectl edit 修改为 NodePort
1
2
3
4
5
6
[root@k8s-master mainfests]# kubectl edit svc/weave-scope-app -n weave
将service的type改为NodePort
service/weave-scope-app edited
[root@k8s-master mainfests]# kubectl get svc -n weave
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
weave-scope-app NodePort 10.97.229.215 <none> 80:32313/TCP 11m

(2)使用 Scope

浏览器访问 http://192.168.56.11:32313/,Scope 默认显示当前所有的 Controller(Deployment、DaemonSet 等)。

img

(3)拓扑结构

Scope 会自动构建应用和集群的逻辑拓扑。比如点击顶部 PODS,会显示所有 Pod 以及 Pod 之间的依赖关系。

img

点击 HOSTS,会显示各个节点之间的关系。

img

(4)实时资源监控

可以在 Scope 中查看资源的 CPU 和内存使用情况。 支持的资源有 Host、Pod 和 Container。

img

(5)在线操作

可以查看 Pod 的日志:

img

(6)强大的搜索功能

Scope 支持关键字搜索和定位资源。

img

3、核心指标监控之metrics-server

在最初的系统资源监控,是通过cAdvisor去收集单个节点以及相关Pod资源的指标数据,但是这一功能仅能够满足单个节点,在集群日益庞大的过程中,该功能就显得low爆了。于是将各个节点的指标数据进行汇聚并通过一个接口进行向外暴露传送是必要的。

Heapster就是这样的一种方式,通过为集群提供指标API和实现并进行监控,它是集群级别的监控和事件数据的聚合工具,但是一个完备的Heapster监控体系是需要进行数据存储的,为此其解决方案就是引入了Influxdb作为后端数据的持久存储,Grafana作为可视化的接口。原理就是Heapster从各个节点上的cAdvisor采集数据并存储到Influxdb中,再由Grafana展示。原理图如下:

img

时代在变迁,陈旧的东西将会被淘汰,由于功能和系统发展的需求,Heapster无法满足k8s系统监控的需求,为此在Kubernetes 1.7版本以后引入了自定义指标(custom metrics API),在1.8版本引入了资源指标(resource metrics API)。逐渐地Heapster用于提供核心指标API的功能也被聚合方式的指标API服务器metrics-server所替代。

在新一代的Kubernetes指标监控体系当中主要由核心指标流水线和监控指标流水线组成:

  • 核心指标流水线:是指由kubelet、、metrics-server以及由API server提供的api组成,它们可以为K8S系统提供核心指标,从而了解并操作集群内部组件和程序。其中相关的指标包括CPU的累积使用率、内存实时使用率,Pod资源占用率以及容器磁盘占用率等等。其中核心指标的获取原先是由heapster进行收集,但是在1.11版本之后已经被废弃,从而由新一代的metrics-server所代替对核心指标的汇聚。核心指标的收集是必要的。如下图:
    img
  • 监控指标流水线:用于从系统收集各种指标数据并提供给终端用户、存储系统以及HPA。它们包含核心指标以及许多非核心指标,其中由于非核心指标本身不能被Kubernetes所解析,此时就需要依赖于用户选择第三方解决方案。如下图:
    img

一个可以同时使用资源指标API和自定义指标API的组件是HPAv2,其实现了通过观察指标实现自动扩容和缩容。而目前资源指标API的实现主流是metrics-server

自1.8版本后,容器的cpu和内存资源占用利用率都可以通过客户端指标API直接调用,从而获取资源使用情况,要知道的是API本身并不存储任何指标数据,仅仅提供资源占用率的实时监测数据。

资源指标和其他的API指标并没有啥区别,它是通过API Server的URL路径/apis/metrics.k8s.io/进行存取,只有在k8s集群内部署了metrics-server应用才能只用API,其简单的结构图如下:

img

Heapster。 Metrics Server 通过 Kubernetes 聚合 器( kube- aggregator) 注册 到 主 API Server 之上, 而后 基于 kubelet 的 Summary API 收集 每个 节 点上 的 指标 数据, 并将 它们 存储 于 内存 中 然后 以 指标 API 格式 提供,如下图:

img

Metrics Server基于 内存 存储, 重 启 后 数据 将 全部 丢失, 而且 它 仅能 留存 最近 收集 到 的 指标 数据, 因此, 如果 用户 期望 访问 历史 数据, 就不 得不 借助于 第三方的监控系统( 如 Prometheus 等)。

一般说来, Metrics Server 在 每个 集群 中 仅 会 运行 一个 实例, 启动 时, 它将 自动 初始化 与 各 节点 的 连接, 因此 出于 安全 方面 的 考虑, 它 需要 运行 于 普通 节点 而非 Master 主机 之上。 直接 使用 项目 本身 提供 的 资源 配置 清单 即 能 轻松 完成 metrics- server 的 部署。

官方YAML链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#从官方YAML文件中下载以下文件
[root@k8s-master metrics]# ll
total 24
-rw-r--r-- 1 root root 398 Mar 22 02:52 auth-delegator.yaml
-rw-r--r-- 1 root root 419 Mar 22 02:52 auth-reader.yaml
-rw-r--r-- 1 root root 393 Mar 22 02:53 metrics-apiservice.yaml
-rw-r--r-- 1 root root 2905 Mar 22 03:46 metrics-server-deployment.yaml
-rw-r--r-- 1 root root 336 Mar 22 02:53 metrics-server-service.yaml
-rw-r--r-- 1 root root 817 Mar 22 03:53 resource-reader.yaml

#修改下面这个文件的部分内容
[root@k8s-master metrics]# vim metrics-server-deployment.yaml

#在metrics-server这个容器字段里面修改command为如下:
spec:
priorityClassName: system-cluster-critical
serviceAccountName: metrics-server
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server-amd64:v0.3.1
command:
- /metrics-server
- --metric-resolution=30s
- --kubelet-insecure-tls
- --kubelet-preferred-address-types=InternalIP,Hostname,InternalDNS,ExternalDNS,ExternalIP

#再修改metrics-server-nanny容器中的cpu和内存值,如下:
command:
- /pod_nanny
- --config-dir=/etc/config
- --cpu=100m
- --extra-cpu=0.5m
- --memory=100Mi
- --extra-memory=50Mi
- --threshold=5
- --deployment=metrics-server-v0.3.1
- --container=metrics-server
- --poll-period=300000
- --estimator=exponential
- --minClusterSize=10

#由于启动容器还需要权限获取数据,需要在resource-reader.yaml文件中增加nodes/stats
[root@k8s-master metrics]# vim resource-reader.yaml
....
rules:
- apiGroups:
- ""
resources:
- pods
- nodes
- nodes/stats
- namespaces

#部署开始
[root@k8s-master metrics]# kubectl apply -f .

[root@k8s-master metrics]# kubectl api-versions |grep metrics
metrics.k8s.io/v1beta1

#检查资源指标API的可用性
[root@k8s-master metrics]# kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes"
{"kind":"NodeMetricsList","apiVersion":"metrics.k8s.io/v1beta1","metadata":{"selfLink":"/apis/metrics.k8s.io/v1beta1/nodes"},"items":[{"metadata":{"name":"k8s-master","selfLink":"/apis/metrics.k8s.io/v1beta1/nodes/k8s-master","creationTimestamp":"2019-03-22T08:12:44Z"},"timestamp":"2019-03-22T08:12:10Z","window":"30s","usage":{"cpu":"522536968n","memory":"1198508Ki"}},{"metadata":{"name":"k8s-node01","selfLink":"/apis/metrics.k8s.io/v1beta1/nodes/k8s-node01","creationTimestamp":"2019-03-22T08:12:44Z"},"timestamp":"2019-03-22T08:12:08Z","window":"30s","usage":{"cpu":"70374658n","memory":"525544Ki"}},{"metadata":{"name":"k8s-node02","selfLink":"/apis/metrics.k8s.io/v1beta1/nodes/k8s-node02","creationTimestamp":"2019-03-22T08:12:44Z"},"timestamp":"2019-03-22T08:12:11Z","window":"30s","usage":{"cpu":"68437841n","memory":"519756Ki"}}]}

#确保Pod对象运行正常
[root@k8s-master metrics]# kubectl get pods -n kube-system |grep metrics
metrics-server-v0.3.1-5977577c75-wrcrt 2/2 Running 0 22m

以上如果内容没有做修改的话,会出现容器跑不起来一直处于CrashLoopBackOff状态,或者出现权限拒绝的问题。可以通过kubectl logs进行查看相关的日志。下面使用kubectl top命令进行查看资源信息:

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master metrics]# kubectl top node
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
k8s-master 497m 12% 1184Mi 68%
k8s-node01 81m 8% 507Mi 58%
k8s-node02 63m 6% 505Mi 57%


[root@k8s-master metrics]# kubectl top pod -l k8s-app=kube-dns --containers=true -n kube-system
POD NAME CPU(cores) MEMORY(bytes)
coredns-78fcdf6894-nmcmz coredns 5m 12Mi
coredns-78fcdf6894-p5pfm coredns 5m 15Mi

k8s学习笔记-20-K8S组件运行原理详解总结

[TOC]

一、看图说K8S

先从一张大图来观看一下K8S是如何运作的,再具体去细化K8S的概念、组件以及网络模型。

img

从上图,我们可以看到K8S组件和逻辑及其复杂,但是这并不可怕,我们从宏观上先了解K8S是怎么用的,再进行庖丁解牛。从上图我们可以看出:

  • Kubernetes集群主要由Master和Node两类节点组成
  • Master的组件包括:apiserver、controller-manager、scheduler和etcd等几个组件,其中apiserver是整个集群的网关。
  • Node主要由kubelet、kube-proxy、docker引擎等组件组成。kubelet是K8S集群的工作与节点上的代理组件。
  • 一个完整的K8S集群,还包括CoreDNS、Prometheus(或HeapSter)、Dashboard、Ingress Controller等几个附加组件。其中cAdivsor组件作用于各个节点(master和node节点)之上,用于收集及收集容器及节点的CPU、内存以及磁盘资源的利用率指标数据,这些统计数据由Heapster聚合后,可以通过apiserver访问。

要了解K8S的所有组件,没去走一遍,永远不知道它是怎么跑起来的,那么下面就带着几个新手疑问来看K8S

  • 1、K8S是如何对容器编排?

在K8S集群中,容器并非最小的单位,K8S集群中最小的调度单位是Pod,容器则被封装在Pod之中。由此可知,一个容器或多个容器可以同属于在一个Pod之中。

  • 2、Pod是怎么创建出来的?

Pod并不是无缘无故跑出来的,它是一个抽象的l逻辑概念,那么Pod是如何创建的呢?Pod是由Pod控制器进行管理控制,其代表性的Pod控制器有Deployment、StatefulSet等。这里我们先有这样的一个概念,后面再详细解刨。

  • 3、Pod资源组成的应用如何提供外部访问的?

Pod组成的应用是通过Service这类抽象资源提供内部和外部访问的,但是service的外部访问需要端口的映射,带来的是端口映射的麻烦和操作的繁琐。为此还有一种提供外部访问的资源叫做Ingress。

  • 4、Service又是怎么关联到Pod呢?

在上面说的Pod是由Pod控制器进行管理控制,对Pod资源对象的期望状态进行自动管理。而在Pod控制器是通过一个YAML的文件进行定义Pod资源对象的。在该文件中,还会对Pod资源对象进行打标签,用于Pod的辨识,而Servcie就是通过标签选择器,关联至同一标签类型的Pod资源对象。这样就实现了从service–>pod–>container的一个过程。

  • 5、Pod的怎么创建逻辑流程是怎样的?
  • (1)客户端提交创建请求,可以通过API Server的Restful API,也可以使用kubectl命令行工具。支持的数据类型包括JSON和YAML。
  • (2)API Server处理用户请求,存储Pod数据到etcd。
  • (3)调度器通过API Server查看未绑定的Pod。尝试为Pod分配主机。
  • (4)过滤主机 (调度预选):调度器用一组规则过滤掉不符合要求的主机。比如Pod指定了所需要的资源量,那么可用资源比Pod需要的资源量少的主机会被过滤掉。
  • (5)主机打分(调度优选):对第一步筛选出的符合要求的主机进行打分,在主机打分阶段,调度器会考虑一些整体优化策略,比如把容一个Replication Controller的副本分布到不同的主机上,使用最低负载的主机等。
  • (6)选择主机:选择打分最高的主机,进行binding操作,结果存储到etcd中。
  • (7)kubelet根据调度结果执行Pod创建操作: 绑定成功后,scheduler会调用APIServer的API在etcd中创建一个boundpod对象,描述在一个工作节点上绑定运行的所有pod信息。运行在每个工作节点上的kubelet也会定期与etcd同步boundpod信息,一旦发现应该在该工作节点上运行的boundpod对象没有更新,则调用Docker API创建并启动pod内的容器。

从上面的几个疑问,大致了解了K8S怎么工作的,那么现在再从三个面去了解Kubernetes,分别是Kubernetes概念和术语、集群组件、网络模型。

二、K8S的概念和术语

Kubernetes是利用共享网络将多个物理机或者虚拟机组成一个集群,在各个服务器之间进行通信,该集群是配置Kubernetes的所有租金啊啊、功能和负载的物理平台。

一个Kubernetes集群由master和node组成。如下图:

  • Master:是集群的网关和中枢枢纽,主要作用:暴露API接口,跟踪其他服务器的健康状态、以最优方式调度负载,以及编排其他组件之间的通信。单个的Master节点可以完成所有的功能,但是考虑单点故障的痛点,生产环境中通常要部署多个Master节点,组成Cluster。
  • Node:是Kubernetes的工作节点,负责接收来自Master的工作指令,并根据指令相应地创建和销毁Pod对象,以及调整网络规则进行合理路由和流量转发。生产环境中,Node节点可以有N个。

img

Kubernetes从宏观上看分为2个角色:Master和Node,但是在Master节点和Node节点上都存在着多个组件来支持内部的业务逻辑,其包括:运行应用、应用编排、服务暴露、应用恢复等等,在Kubernetes中这些概念被抽象为Pod、Service、Controller等资源类型。先来了解一下这些常用概念和术语:

  • (1)Pod

    从上图,我们可以看到K8S并不直接地运行容器,而是被一个抽象的资源对象–Pod所封装,它是K8S最小的调度单位。这里要注意的是,Pod可以封装一个活多个容器!同一个Pod中共享网络名称空间和存储资源,而容器之间可以通过本地回环接口:lo 直接通信,但是彼此之间又在Mount、User和Pid等名称空间上保持了隔离。

  • (2)资源标签(Label)

    标签(Label)是将资源进行分类的标识符,就好像超市的商品分类一般。资源标签具体化的就是一个键值型(key/values)数据,相信了解redis的友友应该知道什么是键值数据。使用标签是为了对指定对象进行辨识,比如Pod对象。标签可以在对象创建时进行附加,也可以创建后进行添加或修改。要知道的是一个对象可以有多个标签,一个标签页可以附加到多个对象。如图:

img

  • (3)标签选择器(Selector)

    有标签,当然就有标签选择器,它是根据Label进行过滤符合条件的资源对象的一种 机制。比如将含有标签role: backend的所有Pod对象挑选出来归并为一组。通常在使用过程中,会通过标签对资源对象进行分类,然后再通过标签选择器进行筛选,最常见的应用就是讲一组这样的Pod资源对象创建为某个Service的端点。如图:

img

  • (4)Pod控制器(Controller)

    虽然Pod是K8S的最小调度单位,但是K8S并不会直接地部署和管理Pod对象,而是要借助于另外一个抽象资源–Controller进行管理。其实一种管理Pod生命周期的资源抽象,并且它是一类对象,并非单个的资源对象,其中包括:ReplicationController、ReplicaSet、Deployment、StatefulSet、Job等。

    以Deployment为例,它负责确保定义的Pod对象的副本数量符合预期的设置,这样用户只需要声明应用的期望状态,控制器就会自动地对其进行管理。如图:

img

  • (5)服务资源(Service)

Service是建立在一组Pod对象之上的资源对象,在前面提过,它是通过标签选择器选择一组Pod对象,并为这组Pod对象定义一个统一的固定访问入口(通常是一个IP地址),如果K8S存在DNS附件(如coredns)它就会在Service创建时为它自动配置一个DNS名称,用于客户端进行服务发现。

通常我们直接请求Service IP,该请求就会被负载均衡到后端的端点,即各个Pod对象,从这点上,是不是有点像负载均衡器呢,因此Service本质上是一个4层的代理服务,另外Service还可以将集群外部流量引入至集群,这就需要节点对Service的端口进行映射了。

  • (6)存储卷(Volume)

在使用容器时,我们知道,当数据存放于容器之中,容器销毁后,数据也会随之丢失。这就是需要一个外部存储,以保证数据的持久化存储。而存储卷就是这样的一个东西。

存储卷(Volume)是独立于容器文件系统之外的存储空间,常用于扩展容器的存储空间并为其提供持久存储能力。存储卷在K8S中的分类为:临时卷、本地卷和网络卷。临时卷和本地卷都位于Node本地,一旦Pod被调度至其他Node节点,此类型的存储卷将无法被访问,因为临时卷和本地卷通常用于数据缓存,持久化的数据通常放置于持久卷(persistent volume)之中。

  • (7)Name和Namespace

名称(Name)是K8S集群中资源对象的标识符,通常作用于名称空间(Namespace),因此名称空间是名称的额外的限定机制。在同一个名称空间中,同一类型资源对象的名称必须具有唯一性。

名称空间通常用于实现租户或项目的资源隔离,从而形成逻辑分组。关于此概念可以参考:https://www.jb51.net/article/136411.htm

如图:创建的Pod和Service等资源对象都属于名称空间级别,未指定时,都属于默认的名称空间default

img

  • (8)注解(Annotation)

Annotation是另一种附加在对象上的一种键值类型的数据,常用于将各种非标识型元数据(metadata)附加到对象上,但它并不能用于标识和选择对象。其作用是方便工具或用户阅读及查找。

  • (9)Ingress

K8S将Pod对象和外部的网络环境进行了隔离,Pod和Service等对象之间的通信需要通过内部的专用地址进行,如果需要将某些Pod对象提供给外部用户访问,则需要给这些Pod对象打开一个端口进行引入外部流量,除了Service以外,Ingress也是实现提供外部访问的一种方式。

三、K8S集群组件

img

1、Master组件

  • 1、API Server

K8S对外的唯一接口,提供HTTP/HTTPS RESTful API,即kubernetes API。所有的请求都需要经过这个接口进行通信。主要负责接收、校验并响应所有的REST请求,结果状态被持久存储在etcd当中,所有资源增删改查的唯一入口。

  • 2、etcd

负责保存k8s 集群的配置信息和各种资源的状态信息,当数据发生变化时,etcd会快速地通知k8s相关组件。etcd是一个独立的服务组件,并不隶属于K8S集群。生产环境当中etcd应该以集群方式运行,以确保服务的可用性。

etcd不仅仅用于提供键值数据存储,而且还为其提供了监听(watch)机制,用于监听和推送变更。在K8S集群系统中,etcd的键值发生变化会通知倒API Server,并由其通过watch API向客户端输出。

  • 3、Controller Manager

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

  • 生命周期功能:包括Namespace创建和生命周期、Event垃圾回收、Pod终止相关的垃圾回收、级联垃圾回收及Node垃圾回收等。
  • API业务逻辑:例如,由ReplicaSet执行的Pod扩展等。
  • 4、调度器(Schedule)

资源调度,负责决定将Pod放到哪个Node上运行。Scheduler在调度时会对集群的结构进行分析,当前各个节点的负载,以及应用对高可用、性能等方面的需求。

2、Node组件

Node主要负责提供容器的各种依赖环境,并接受Master管理。每个Node有以下几个组件构成。

  • 1、Kubelet

kubelet是node的agent,当Scheduler确定在某个Node上运行Pod后,会将Pod的具体配置信息(image、volume等)发送给该节点的kubelet,kubelet会根据这些信息创建和运行容器,并向master报告运行状态。

  • 2、Container Runtime

每个Node都需要提供一个容器运行时(Container Runtime)环境,它负责下载镜像并运行容器。目前K8S支持的容器运行环境至少包括Docker、RKT、cri-o、Fraki等。

  • 3、Kube-proxy

service在逻辑上代表了后端的多个Pod,外借通过service访问Pod。service接收到请求就需要kube-proxy完成转发到Pod的。每个Node都会运行kube-proxy服务,负责将访问的service的TCP/UDP数据流转发到后端的容器,如果有多个副本,kube-proxy会实现负载均衡,有2种方式:LVS或者Iptables

3、核心附件

K8S集群还依赖一组附件组件,通常是由第三方提供的特定应用程序。如下图:

img

  • 1、KubeDNS

在K8S集群中调度并运行提供DNS服务的Pod,同一集群内的其他Pod可以使用该DNS服务来解决主机名。K8S自1.11版本开始默认使用CoreDNS项目来为集群提供服务注册和服务发现的动态名称解析服务。

  • 2、Dashboard

K8S集群的全部功能都要基于Web的UI,来管理集群中的应用和集群自身。

  • 3、Heapster

容器和节点的性能监控与分析系统,它收集并解析多种指标数据,如资源利用率、生命周期时间,在最新的版本当中,其主要功能逐渐由Prometheus结合其他的组件进行代替。

  • 4、Ingress Controller

Service是一种工作于4层的负载均衡器,而Ingress是在应用层实现的HTTP(S)的负载均衡。不过,Ingress资源自身并不能进行流量的穿透,,它仅仅是一组路由规则的集合,这些规则需要通过Ingress控制器(Ingress Controller)发挥作用。目前该功能项目大概有:Nginx-ingress、Traefik、Envoy和HAproxy等。如下图就是Nginx-ingress的应用,具体可以查看博文:https://www.cnblogs.com/linuxk/p/9706720.html

img

四、K8S的网络模型

K8S的网络中主要存在4种类型的通信:

  • ①同一Pod内的容器间通信
  • ②各个Pod彼此间的通信
  • ③Pod和Service间的通信
  • ④集群外部流量和Service之间的通信

K8S为Pod和Service资源对象分别使用了各自的专有网络,Pod网络由K8S的网络插件配置实现,而Service网络则由K8S集群进行指定。如下图:

img

K8S使用的网络插件需要为每个Pod配置至少一个特定的地址,即Pod IP。Pod IP地址实际存在于某个网卡(可以是虚拟机设备)上。

而Service的地址却是一个虚拟IP地址,没有任何网络接口配置在此地址上,它由Kube-proxy借助iptables规则或ipvs规则重定向到本地端口,再将其调度到后端的Pod对象。Service的IP地址是集群提供服务的接口,也称为Cluster IP。

Pod网络和IP由K8S的网络插件负责配置和管理,具体使用的网络地址可以在管理配置网络插件时进行指定,如10.244.0.0/16网络。而Cluster网络和IP是由K8S集群负责配置和管理,如10.96.0.0/12网络。

从上图进行总结起来,一个K8S集群包含是三个网络。

  • **(1)节点网络:**各主机(Master、Node、ETCD等)自身所属的网络,地址配置在主机的网络接口,用于各主机之间的通信,又称为节点网络。
  • **(2)Pod网络:**专用于Pod资源对象的网络,它是一个虚拟网络,用于为各Pod对象设定IP地址等网络参数,其地址配置在Pod中容器的网络接口上。Pod网络需要借助kubenet插件或CNI插件实现。
  • **(3)Service网络:**专用于Service资源对象的网络,它也是一个虚拟网络,用于为K8S集群之中的Service配置IP地址,但是该地址不会配置在任何主机或容器的网络接口上,而是通过Node上的kube-proxy配置为iptables或ipvs规则,从而将发往该地址的所有流量调度到后端的各Pod对象之上。

五、Kubernetes的核心对象详解

1、Pod资源对象

Pod资源对象是一种集合了一个或多个应用容器、存储资源、专用ip、以及支撑运行的其他选项的逻辑组件。如下图:Pod其实就是一个应用程序运行的单一实例,它通常由共享资源且关系紧密的一个或2多个应用容器组成。

img

Kubernetes的网络模型要求每个Pod的IP地址同一IP网段,各个Pod之间可以使用IP地址进行通信,无论这些Pod运行在集群内的哪个节点上,这些Pod对象都类似于运行在同一个局域网内的虚拟机一般。

我们可以将每一个Pod对象类比为一个物理主机或者是虚拟机,那么运行在同一个Pod对象中的多个进程,也就类似于跑在物理主机上的独立进程,而不同的是Pod对象中的各个进程都运行在彼此隔离的容器当中,而各个容器之间共享两种关键性资源:网络和存储卷。

  • 网络:每一个Pod对象都会分配到一个Pod IP地址,同一个Pod内部的所有容器共享Pod对象的Network和UTS名称空间,其中包括主机名、IP地址和端口等。因此,这些容器可以通过本地的回环接口lo进行通信,而在Pod之外的其他组件的通信,则需要使用Service资源对象的Cluster IP+端口完成。
  • 存储卷:用户可以给Pod对象配置一组存储卷资源,这些资源可以共享给同一个Pod中的所有容器使用,从而完成容器间的数据共享。存储卷还可以确保在容器终止后被重启,或者是被删除后也能确保数据的持久化存储。

一个Pod代表着某个应用程序的特定实例,如果我们需要去扩展这个应用程序,那么就意味着需要为该应用程序同时创建多个Pod实例,每个实例都代表着应用程序的一个运行副本。而这些副本化的Pod对象的创建和管理,都是由一组称为Controller的对象实现,比如Deployment控制器对象。

当创建Pod时,我们还可以使用Pod Preset对象为Pod注入特定的信息,比如Configmap、Secret、存储卷、卷挂载、环境变量等。有了Pod Preset对象,Pod模板的创建就不需要为每个模板显示提供所有信息。

基于预定的期望状态和各个节点的资源可用性,Master会把Pod对象调度至选定的工作节点上运行,工作节点从指向的镜像仓库进行下载镜像,并在本地的容器运行时环境中启动容器。Master会将整个集群的状态保存在etcd中,并通过API Server共享给集群的各个组件和客户端。

2、Controller

在K8S的集群设计中,Pod是一个有生命周期的对象。那么用户通过手工创建或者通过Controller直接创建的Pod对象会被调度器(Scheduler)调度到集群中的某个工作节点上运行,等到容器应用进程运行结束之后正常终止,随后就会被删除。而需要注意的是,当节点的资源耗尽或者故障,也会导致Pod对象的回收。

而K8S在这一设计上,使用了控制器实现对一次性的Pod对象进行管理操作。比如,要确保部署的应用程序的Pod副本数达到用户预期的数目,以及基于Pod模板来重建Pod对象等,从而实现Pod对象的扩容、缩容、滚动更新和自愈能力。例如,在某个节点故障,相关的控制器会将运行在该节点上的Pod对象重新调度到其他节点上进行重建。

控制器本身也是一种资源类型,其中包括Replication、Controller、Deployment、StatefulSet、DaemonSet、Jobs等等,它们都统称为Pod控制器。如下图的Deployment就是这类控制器的代表实现,是目前用于管理无状态应用的Pod控制器。

img

Pod控制器的定义通常由期望的副本数量、Pod模板、标签选择器组成。Pod控制器会根据标签选择器来对Pod对象的标签进行匹配筛选,所有满足选择条件的Pod对象都会被当前控制器进行管理并计入副本总数,确保数目能够达到预期的状态副本数。

需要注意的是,在实际的应用场景中,在接收到的请求流量负载低于或接近当前已有Pod副本的承载能力时,需要我们手动修改Pod控制器中的期望副本数量以实现应用规模的扩容和缩容。而在集群中部署了HeapSet或者Prometheus的这一类资源监控组件时,用户还可以通过HPA(HorizontalPodAutoscaler)来计算出合适的Pod副本数量,并自动地修改Pod控制器中期望的副本数,从而实现应用规模的动态伸缩,提高集群资源的利用率。

K8S集群中的每个节点上都运行着cAdvisor,用于收集容器和节点的CPU、内存以及磁盘资源的利用率直播数据,这些统计数据由Heapster聚合之后可以通过API server访问。而HorizontalPodAutoscaler基于这些统计数据监控容器的健康状态并作出扩展决策。

3、Service

我们知道Pod对象有Pod IP地址,但是该地址无法确保Pod对象重启或者重建之后保持不变,这会带来集群中Pod应用间依赖关系维护的麻烦。比如前段Pod应用无法基于固定的IP地址跟中后端的Pod应用。

而Service资源就是在被访问的Pod对象中添加一个有着固定IP地址的中间层,客户端向该地址发起访问请求后,由相关的Service资源进行调度并代理到后端的Pod对象。

Service并不是一个具体的组件,而是一个通过规则定义出由多个Pod对象组成而成的逻辑集合,并附带着访问这组Pod对象的策略。Service对象挑选和关联Pod对象的方式和Pod控制器是一样的,都是通过标签选择器进行定义。如下图:

img

Service IP是一种虚拟IP,也称为Cluster IP,专用于集群内通信,通常使用专有的地址段,如:10.96.0.0/12网络,各Service对象的IP地址在该范围内由系统动态分配。

集群内的Pod对象可直接请求这类Cluster IP,比如上图中来自Pod client的访问请求,可以通过Service的Cluster IP作为目标地址进行访问,但是在集群网络中是属于私有的网络地址,仅仅可以在集群内部访问。

而需要将集群外部的访问引入集群内部的常用方法是通过节点网络进行,其实现方法如下:

  • 通过工作节点的IP地址+端口(Node Port)接入请求。
  • 将该请求代理到相应的Service对象的Cluster IP的服务端口上,通俗地说:就是工作节点上的端口映射了Service的端口。
  • 由Service对象将该请求转发到后端的Pod对象的Pod IP和 应用程序的监听端口。

因此,类似于上图来自Exxternal Client的集群外部客户端,是无法直接请求该Service的Cluster IP,而是需要实现经过某一工作节点(如 Node Y)的IP地址,着了请求需要2次转发才能到目标Pod对象。这一类访问的缺点就是在通信效率上有一定的延时。

k8s学习笔记-22-Pod资源调度

[TOC]

API Server在接受客户端提交Pod对象创建请求后,然后是通过调度器(kube-schedule)从集群中选择一个可用的最佳节点来创建并运行Pod。而这一个创建Pod对象,在调度的过程当中有3个阶段:节点预选、节点优选、节点选定,从而筛选出最佳的节点。如图:

img

  • 节点预选:基于一系列的预选规则对每个节点进行检查,将那些不符合条件的节点过滤,从而完成节点的预选
  • 节点优选:对预选出的节点进行优先级排序,以便选出最合适运行Pod对象的节点
  • 节点选定:从优先级排序结果中挑选出优先级最高的节点运行Pod,当这类节点多于1个时,则进行随机选择

当我们有需求要将某些Pod资源运行在特定的节点上时,我们可以通过组合节点标签,以及Pod标签或标签选择器来匹配特定的预选策略并完成调度,如MatchInterPodAfinity、MatchNodeSelector、PodToleratesNodeTaints等预选策略,这些策略常用于为用户提供自定义Pod亲和性或反亲和性、节点亲和性以及基于污点及容忍度的调度机制。

1、常用的预选策略

预选策略实际上就是节点过滤器,例如节点标签必须能够匹配到Pod资源的标签选择器(MatchNodeSelector实现的规则),以及Pod容器的资源请求量不能大于节点上剩余的可分配资源(PodFitsResource规则)等等。执行预选操作,调度器会逐一根据规则进行筛选,如果预选没能选定一个合适的节点,此时Pod会一直处于Pending状态,直到有一个可用节点完成调度。其常用的预选策略如下:

  • CheckNodeCondition:检查是否可以在节点报告磁盘、网络不可用或未准备好的情况下将Pod对象调度其上。
  • HostName:如果Pod对象拥有spec.hostname属性,则检查节点名称字符串是否和该属性值匹配。
  • PodFitsHostPorts:如果Pod对象定义了ports.hostPort属性,则检查Pod指定的端口是否已经被节点上的其他容器或服务占用。
  • MatchNodeSelector:如果Pod对象定义了spec.nodeSelector属性,则检查节点标签是否和该属性匹配。
  • NoDiskConflict:检查Pod对象请求的存储卷在该节点上可用。
  • PodFitsResources:检查节点上的资源(CPU、内存)可用性是否满足Pod对象的运行需求。
  • PodToleratesNodeTaints:如果Pod对象中定义了spec.tolerations属性,则需要检查该属性值是否可以接纳节点定义的污点(taints)。
  • PodToleratesNodeNoExecuteTaints:如果Pod对象定义了spec.tolerations属性,检查该属性是否接纳节点的NoExecute类型的污点。
  • CheckNodeLabelPresence:仅检查节点上指定的所有标签的存在性,要检查的标签以及其可否存在取决于用户的定义。
  • CheckServiceAffinity:根据当前Pod对象所属的Service已有其他Pod对象所运行的节点调度,目前是将相同的Service的Pod对象放在同一个或同一类节点上。
  • MaxEBSVolumeCount:检查节点上是否已挂载EBS存储卷数量是否超过了设置的最大值,默认值:39
  • MaxGCEPDVolumeCount:检查节点上已挂载的GCE PD存储卷是否超过了设置的最大值,默认值:16
  • MaxAzureDiskVolumeCount:检查节点上已挂载的Azure Disk存储卷数量是否超过了设置的最大值,默认值:16
  • CheckVolumeBinding:检查节点上已绑定和未绑定的PVC是否满足Pod对象的存储卷需求。
  • NoVolumeZoneConflct:在给定了区域限制的前提下,检查在该节点上部署Pod对象是否存在存储卷冲突。
  • CheckNodeMemoryPressure:在给定了节点已经上报了存在内存资源压力过大的状态,则需要检查该Pod是否可以调度到该节点上。
  • CheckNodePIDPressure:如果给定的节点已经报告了存在PID资源压力过大的状态,则需要检查该Pod是否可以调度到该节点上。
  • CheckNodeDiskPressure:如果给定的节点存在磁盘资源压力过大,则检查该Pod对象是否可以调度到该节点上。
  • MatchInterPodAffinity:检查给定的节点能否可以满足Pod对象的亲和性和反亲和性条件,用来实现Pod亲和性调度或反亲和性调度。

在上面的这些预选策略里面,CheckNodeLabelPressure和CheckServiceAffinity可以在预选过程中结合用户自定义调度逻辑,这些策略叫做可配置策略。其他不接受参数进行自定义配置的称为静态策略。

2、优选函数

预选策略筛选出一个节点列表就会进入优选阶段,在这个过程调度器会向每个通过预选的节点传递一系列的优选函数来计算其优先级分值,优先级分值介于0-10之间,其中0表示不适用,10表示最适合托管该Pod对象。

另外,调度器还支持给每个优选函数指定一个简单的值,表示权重,进行节点优先级分值计算时,它首先将每个优选函数的计算得分乘以权重,然后再将所有优选函数的得分相加,从而得出节点的最终优先级分值。权重可以让管理员定义优选函数倾向性的能力,其计算优先级的得分公式如下:

1
finalScoreNode = (weight1 * priorityFunc1) + (weight2 * priorityFunc2) + ......

下图是关于优选函数的列表图:

img

3、节点亲和调度

节点亲和性是用来确定Pod对象调度到哪一个节点的规则,这些规则基于节点上的自定义标签和Pod对象上指定的标签选择器进行定义。

定义节点亲和性规则有2种:硬亲和性(require)和软亲和性(preferred)

  • 硬亲和性:实现的是强制性规则,是Pod调度时必须满足的规则,否则Pod对象的状态会一直是Pending
  • 软亲和性:实现的是一种柔性调度限制,在Pod调度时可以尽量满足其规则,在无法满足规则时,可以调度到一个不匹配规则的节点之上。

定义节点亲和规则的两个要点:一是节点配置是否合乎需求的标签,而是Pod对象定义合理的标签选择器,这样才能够基于标签选择出期望的目标节点。

需要注意的是preferredDuringSchedulingIgnoredDuringExecutionrequiredDuringSchedulingIgnoredDuringExecution名字中后半段字符串IgnoredDuringExecution表示的是,在Pod资源基于节点亲和性规则调度到某个节点之后,如果节点的标签发生了改变,调度器不会讲Pod对象从该节点上移除,因为该规则仅对新建的Pod对象有效。

3.1、节点硬亲和性

下面的配置清单中定义的Pod对象,使用节点硬亲和性和规则定义将当前Pod调度到标签为zone=foo的节点上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
apiVersion: v1
kind: Pod
metadata:
name: with-require-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone,operator: In,values: ["foo"]}
containers:
- name: myapp
image: ikubernetes/myapp:v1

#创建Pod对象
[root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml
pod/with-require-nodeaffinity created

#由于集群中并没有节点含有节点标签为zone=foo,所以创建的Pod一直处于Pending状态
[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-require-nodeaffinity 0/1 Pending 0 35s

#查看Pending具体的原因
[root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity
......
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 3s (x21 over 1m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.

#给node01节点打上zone=foo的标签,可以看到成功调度到node01节点上
[root@k8s-master ~]# kubectl label node k8s-node01 zone=foo
node/k8s-node01 labeled
[root@k8s-master ~]# kubectl describe pods with-require-nodeaffinity
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 58s (x25 over 2m) default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector.
Normal Pulled 4s kubelet, k8s-node01 Container image "ikubernetes/myapp:v1" already present on machine
Normal Created 4s kubelet, k8s-node01 Created container
Normal Started 4s kubelet, k8s-node01 Started container

[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity -o wide
NAME READY STATUS RESTARTS AGE IP NODE
with-require-nodeaffinity 1/1 Running 0 6m 10.244.1.12 k8s-node01

在定义节点亲和性时,requiredDuringSchedulingIgnoredDuringExecution字段的值是一个对象列表,用于定义节点硬亲和性,它可以由一个或多个nodeSelectorTerms定义的对象组成,此时值需要满足其中一个nodeSelectorTerms即可。

nodeSelectorTerms用来定义节点选择器的条目,它的值也是一个对象列表,由1个或多个matchExpressions对象定义的匹配规则组成,多个规则是逻辑与的关系,这就表示某个节点的标签必须要满足同一个nodeSelectorTerms下所有的matchExpression对象定义的规则才能够成功调度。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#如下配置清单,必须存在满足标签zone=foo和ssd=true的节点才能够调度成功
apiVersion: v1
kind: Pod
metadata:
name: with-require-nodeaffinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- {key: ssd, operator: Exists, values: []} #增加一个规则
containers:
- name: myapp
image: ikubernetes/myapp:v1

[root@k8s-master ~]# kubectl apply -f require-nodeAffinity-pod.yaml
pod/with-require-nodeaffinity created
[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-require-nodeaffinity 0/1 Pending 0 16s
[root@k8s-master ~]# kubectl label node k8s-node01 ssd=true
node/k8s-node01 labeled

[root@k8s-master ~]# kubectl get pods with-require-nodeaffinity
NAME READY STATUS RESTARTS AGE
with-require-nodeaffinity 1/1 Running 0 2m

在预选策略中,还可以通过节点资源的可用性去限制能够成功调度,如下配置清单:要求的资源为6核心CPU和20G内存,节点是无法满足该容器的资源需求,因此也会调度失败,Pod资源会处于Pending状态。

1
2
3
4
5
6
7
8
9
10
11
12
apiVersion: v1
kind: Pod
metadata:
name: with-require-nodeaffinity
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
resources:
requests:
cpu: 6
memory: 20Gi

3.2、节点软亲和性

看下面一个配置清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deploy-with-node-affinity
spec:
replicas: 5
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp-pod
labels:
app: myapp
spec:
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 60
preference:
matchExpressions:
- {key: zone, operator: In, values: ["foo"]}
- weight: 30
preference:
matchExpressions:
- {key: ssd, operator: Exists, values: []}
containers:
- name: myapp
image: ikubernetes/myapp:v1

#首先先给node02和master都打上标签,master标签为zone=foo,node02标签为ssd=true,这里node01是没有对应标签的
[root@k8s-master ~]# kubectl label node k8s-node02 ssd=true
node/k8s-node02 labeled
[root@k8s-master ~]# kubectl label node k8s-master zone=foo
node/k8s-master labeled

#进行创建
[root@k8s-master ~]# kubectl apply -f deploy-with-preferred-nodeAffinity.yaml
deployment.apps/myapp-deploy-with-node-affinity2 created

#可以看到5个Pod分别分布在不同的节点上,node01上没有对应的标签也会调度上进行创建Pod,体现软亲和性
[root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with
myapp-deploy-with-node-affinity2-75b8f65f87-2gqjv 1/1 Running 0 11s 10.244.2.4 k8s-node02
myapp-deploy-with-node-affinity2-75b8f65f87-7l2sg 1/1 Running 0 11s 10.244.0.4 k8s-master
myapp-deploy-with-node-affinity2-75b8f65f87-cdrxx 1/1 Running 0 11s 10.244.2.3 k8s-node02
myapp-deploy-with-node-affinity2-75b8f65f87-j77f6 1/1 Running 0 11s 10.244.1.36 k8s-node01
myapp-deploy-with-node-affinity2-75b8f65f87-wt6tq 1/1 Running 0 11s 10.244.0.3 k8s-master

#如果我们给node01打上zone=foo,ssd=true的标签,再去创建时,你会发现所有的Pod都调度在这个节点上。因为节点的软亲和性,会尽力满足Pod中定义的规则,如下:
[root@k8s-master ~]# kubectl label node k8s-node01 zone=foo
[root@k8s-master ~]# kubectl label node k8s-node01 ssd=true
[root@k8s-master ~]# kubectl get pods -o wide |grep deploy-with
myapp-deploy-with-node-affinity2-75b8f65f87-4lwsw 0/1 ContainerCreating 0 3s <none> k8s-node01
myapp-deploy-with-node-affinity2-75b8f65f87-dxbxf 1/1 Running 0 3s 10.244.1.31 k8s-node01
myapp-deploy-with-node-affinity2-75b8f65f87-lnhgm 0/1 ContainerCreating 0 3s <none> k8s-node01
myapp-deploy-with-node-affinity2-75b8f65f87-snxbc 0/1 ContainerCreating 0 3s <none> k8s-node01
myapp-deploy-with-node-affinity2-75b8f65f87-zx8ck 1/1 Running 0 3s 10.244.1.33 k8s-node01

上面的实验结果显示,当2个标签没有都存在一个node节点上时,Pod对象会被分散在集群中的三个节点上进行创建并运行,之所以如此,是因为使用了 节点软亲和性的预选方式,所有的节点都能够通过MatchNodeSelector预选策略的筛选。当我们将2个标签都集合在node01上时,所有Pod对象都会运行在node01之上。

4、Pod资源亲和调度

在出于高效通信的需求,有时需要将一些Pod调度到相近甚至是同一区域位置(比如同一节点、机房、区域)等等,比如业务的前端Pod和后端Pod,此时这些Pod对象之间的关系可以叫做亲和性。

同时出于安全性的考虑,也会把一些Pod之间进行隔离,此时这些Pod对象之间的关系叫做反亲和性(anti-affinity)。

调度器把第一个Pod放到任意位置,然后和该Pod有亲和或反亲和关系的Pod根据该动态完成位置编排,这就是Pod亲和性和反亲和性调度的作用。Pod的亲和性定义也存在硬亲和性和软亲和性的区别,其约束的意义和节点亲和性类似。

Pod的亲和性调度要求各相关的Pod对象运行在同一位置,而反亲和性则要求它们不能运行在同一位置。这里的位置实际上取决于节点的位置拓扑,拓扑的方式不同,Pod是否在同一位置的判定结果也会有所不同。

如果基于各个节点的kubernetes.io/hostname标签作为评判标准,那么会根据节点的hostname去判定是否在同一位置区域。

4.1、Pod硬亲和度

Pod强制约束的亲和性调度也是使用requiredDuringSchedulingIgnoredDuringExecution进行定义的。Pod亲和性是用来描述一个Pod对象和现有的Pod对象运行的位置存在某种依赖关系,所以如果要测试Pod亲和性约束,需要存在一个被依赖的Pod对象,下面创建一个带有app=tomcat的Deployment资源部署一个Pod对象:

1
2
3
4
5
[root@k8s-master ~]# kubectl run tomcat -l app=tomcat --image=tomcat:alpine
deployment.apps/tomcat created
[root@k8s-master ~]# kubectl get pods -l app=tomcat -o wide
NAME READY STATUS RESTARTS AGE IP NODE
tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01

从上面我们可以看到新创建的tomcatpod对象被调度在k8s-node01上,再写一个配置清单定义一个Pod对象,通过labelSelector定义的标签选择器挑选对应的Pod对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ~]# vim required-podAffinity-pod1.yaml
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity-1
spec:
affinity:
podAffninity:
requiredDuringSchedulingIngnoreDuringExecution:
- labelSelector:
matchExpression:
- {key: app , operator: In , values: ["tomcat"]}
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: ikubernetes/myapp:v1

kubernetes.io/hostname标签是Kubernetes集群节点的内建标签,它的值为当前节点的主机名,对于各个节点来说都是不同的。所以新建的Pod对象要被部署到和tomcat所在的同一个节点上。

1
2
3
4
5
6
7
[root@k8s-master ~]# kubectl apply -f required-podAffinity-pod1.yaml 
pod/with-pod-affinity-1 created

[root@k8s-master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
with-pod-affinity-1 1/1 Running 0 31s 10.244.1.38 k8s-node01
tomcat-75fd5cc757-w9qdb 1/1 Running 0 5m 10.244.1.37 k8s-node01

基于单一节点的Pod亲和性相对来说使用的情况会比较少,通常使用的是基于同一地区、区域、机架等拓扑位置约束。比如部署应用程序(myapp)和数据库(db)服务相关的Pod时,这两种Pod应该部署在同一区域上,可以加速通信的速度。

4.2、Pod软亲和度

同理,有硬亲和度即有软亲和度,Pod也支持使用preferredDuringSchedulingIgnoredDuringExecuttion属性进行定义Pod的软亲和性,调度器会尽力满足亲和约束的调度,在满足不了约束条件时,也允许将该Pod调度到其他节点上运行。比如下面这一配置清单:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-preferred-pod-affinity
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In , values: ["cache"]}
topologyKey: zone
- weight: 20
podAffinityTerm:
labelSelector:
matchExpressions:
- {key: app, operator: In, values: ["db"]}
topologyKey: zone
containers:
- name: myapp
image: ikubernetes/mapp:v1

上述的清单配置当中,pod的软亲和调度需要将Pod调度到标签为app=cache并在区域zone当中,或者调度到app=db标签节点上的,但是我们的节点上并没有类似的标签,所以调度器会根据软亲和调度进行随机调度到k8s-node01节点之上。如下:

1
2
3
4
5
6
[root@k8s-master ~]# kubectl apply -f deploy-with-preferred-podAffinity.yaml 
deployment.apps/myapp-with-preferred-pod-affinity created
[root@k8s-master ~]# kubectl get pods -o wide |grep myapp-with-preferred-pod-affinity
myapp-with-preferred-pod-affinity-5c44649f58-cwgcd 1/1 Running 0 1m 10.244.1.40 k8s-node01
myapp-with-preferred-pod-affinity-5c44649f58-hdk8q 1/1 Running 0 1m 10.244.1.42 k8s-node01
myapp-with-preferred-pod-affinity-5c44649f58-kg7cx 1/1 Running 0 1m 10.244.1.41 k8s-node01

4.3、Pod反亲和度

podAffinity定义了Pod对象的亲和约束,而Pod对象的反亲和调度则是用podAntiAffinty属性进行定义,下面的配置清单中定义了由同一Deployment创建但是彼此基于节点位置互斥的Pod对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master ~]# cat deploy-with-required-podAntiAffinity.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-with-pod-anti-affinity
spec:
replicas: 4
selector:
matchLabels:
app: myapp
template:
metadata:
name: myapp
labels:
app: myapp
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- {key: app,operator: In,values: ["myapp"]}
topologyKey: kubernetes.io/hostname
containers:
- name: myapp
image: ikubernetes/myapp:v1

[root@k8s-master ~]# kubectl apply -f deploy-with-required-podAntiAffinity.yaml
deployment.apps/myapp-with-pod-anti-affinity created
[root@k8s-master ~]# kubectl get pods -l app=myapp
NAME READY STATUS RESTARTS AGE
myapp-with-pod-anti-affinity-79c7b6c596-77hrz 1/1 Running 0 8s
myapp-with-pod-anti-affinity-79c7b6c596-fhxmv 0/1 Pending 0 8s
myapp-with-pod-anti-affinity-79c7b6c596-l9ckr 1/1 Running 0 8s
myapp-with-pod-anti-affinity-79c7b6c596-vfv2s 1/1 Running 0 8s

由于在配置清单中定义了强制性反亲和性,所以创建的4个Pod副本必须 运行在不同的节点当中呢,但是集群中只存在3个节点,因此,肯定会有一个Pod对象处于Pending的状态。

5、污点和容忍度

污点(taints)是定义在节点上的一组键值型属性数据,用来让节点拒绝将Pod调度到该节点上,除非该Pod对象具有容纳节点污点的容忍度。而容忍度(tolerations)是定义在Pod对象上的键值型数据,用来配置让Pod对象可以容忍节点的污点。

前面的节点选择器和节点亲和性的调度方式都是通过在Pod对象上添加标签选择器来完成对特定类型节点标签的匹配,实现的是Pod选择节点的方式。而污点和容忍度则是通过对节点添加污点信息来控制Pod对象的调度结果,让节点拥有了控制哪种Pod对象可以调度到该节点上的 一种方式。

Kubernetes使用PodToleratesNodeTaints预选策略和TaintTolerationPriority优选函数来完成这种调度方式。

5.1、定义污点和容忍度

污点的定义是在节点的nodeSpec,而容忍度的定义是在Pod中的podSpec,都属于键值型数据,两种方式都支持一个effect标记,语法格式为key=value: effect,其中key和value的用户和格式和资源注解类似,而effect是用来定义对Pod对象的排斥等级,主要包含以下3种类型:

  • NoSchedule:不能容忍此污点的新Pod对象不能调度到该节点上,属于强制约束,节点现存的Pod对象不受影响。
  • PreferNoSchedule:NoSchedule属于柔性约束,即不能容忍此污点的Pod对象尽量不要调度到该节点,不过无其他节点可以调度时也可以允许接受调度。
  • NoExecute:不能容忍该污点的新Pod对象不能调度该节点上,强制约束,节点现存的Pod对象因为节点污点变动或Pod容忍度的变动导致无法匹配规则,Pod对象就会被从该节点上去除。

在Pod对象上定义容忍度时,其支持2中操作符:EqualExists

  • Equal:等值比较,表示容忍度和污点必须在key、value、effect三者之上完全匹配。
  • Exists:存在性判断,表示二者的key和effect必须完全匹配,而容忍度中的value字段使用空值。

在使用kubeadm部署的集群中,master节点上将会自动添加污点信息,阻止不能容忍该污点的Pod对象调度到该节点上,如下:

1
2
3
4
5
6
[root@k8s-master ~]# kubectl describe node k8s-master
Name: k8s-master
Roles: master
......
Taints: node- role. kubernetes. io/ master: NoSchedule
......

而一些系统级别的应用在创建时,就会添加相应的容忍度来确保被创建时可以调度到master节点上,如flannel插件:

1
2
3
4
5
6
7
8
[root@k8s-master ~]# kubectl describe pods kube-flannel-ds-amd64-2p8wm -n kube-system
......
Tolerations: node-role.kubernetes.io/master:NoSchedule
node.kubernetes.io/disk-pressure:NoSchedule
node.kubernetes.io/memory-pressure:NoSchedule
node.kubernetes.io/not-ready:NoExecute
node.kubernetes.io/unreachable:NoExecute
......

5.2、管理节点的污点

使用命令行向节点添加污点

1
2
3
4
5
6
7
8
9
语法:kubectl taint nodes <nodename> <key>=<value>:<effect>......

#定义k8s-node01上的污点
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:NoSchedule
node/k8s-node01 tainted

#查看节点污点信息
[root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}}
[map[effect:NoSchedule key:node-type value:production]]

此时,node01节点上已经存在的Pod对象不受影响,仅对新建Pod对象有影响,需要注意的是,如果是同一个键值数据,但是最后的标识不同,也是属于不同的污点信息,比如再给node01上添加一个污点的标识为:PreferNoSchedule

1
2
3
4
5
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type=production:PreferNoSchedule
node/k8s-node01 tainted

[root@k8s-master ~]# kubectl get nodes k8s-node01 -o go-template={{.spec.taints}}
[map[value:production effect:PreferNoSchedule key:node-type] map[key:node-type value:production effect:NoSchedule]]

删除污点

1
2
3
4
5
6
7
8
9
10
11
12
语法:kubectl taint nodes <node-name> <key>[: <effect>]-

#删除node01上的node-type标识为NoSchedule的污点
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type:NoSchedule-
node/k8s-node01 untainted

#删除指定键名的所有污点
[root@k8s-master ~]# kubectl taint nodes k8s-node01 node-type-
node/k8s-node01 untainted

#补丁方式删除节点上的全部污点信息
[root@k8s-master ~]# kubectl patch nodes k8s-node01 -p '{"spec":{"taints":[]}}'

5.3、Pod对象的容忍度

Pod对象的容忍度可以通过spec.tolerations字段进行添加,同一的也有两种操作符:EqualExists方式。Equal等值方式如下:

1
2
3
4
5
6
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "Noexecute"
tolerationSeconds: 3600

Exists方式如下:

1
2
3
4
5
tolerations: 
- key: "key1"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 3600

k8s学习笔记-21-k8s的网络模型和网络策略

[TOC]

1、Kubernetes网络模型和CNI插件

在Kubernetes中设计了一种网络模型,要求无论容器运行在集群中的哪个节点,所有容器都能通过一个扁平的网络平面进行通信,即在同一IP网络中。需要注意的是:在K8S集群中,IP地址分配是以Pod对象为单位,而非容器,同一Pod内的所有容器共享同一网络名称空间。

1.1、Docker网络模型

了解Docker的友友们都应该清楚,Docker容器的原生网络模型主要有3种:Bridge(桥接)、Host(主机)、none。

  • Bridge:借助虚拟网桥设备为容器建立网络连接。
  • Host:设置容器直接共享当前节点主机的网络名称空间。
  • none:多个容器共享同一个网络名称空间。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#使用以下命令查看docker原生的三种网络
[root@localhost ~]# docker network ls
NETWORK ID NAME DRIVER SCOPE
0efec019c899 bridge bridge local
40add8bb5f07 host host local
ad94f0b1cca6 none null local

#none网络,在该网络下的容器仅有lo网卡,属于封闭式网络,通常用于对安全性要求较高并且不需要联网的应用
[root@localhost ~]# docker run -it --network=none busybox
/ # ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)

#host网络,共享宿主机的网络名称空间,容器网络配置和host一致,但是存在端口冲突的问题
[root@localhost ~]# docker run -it --network=host busybox
/ # ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
link/ether 00:0c:29:69:a7:23 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.4/24 brd 192.168.1.255 scope global dynamic eth0
valid_lft 84129sec preferred_lft 84129sec
inet6 fe80::20c:29ff:fe69:a723/64 scope link
valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue
link/ether 02:42:29:09:8f:dd brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:29ff:fe09:8fdd/64 scope link
valid_lft forever preferred_lft forever
/ # hostname
localhost

#bridge网络,Docker安装完成时会创建一个名为docker0的linux bridge,不指定网络时,创建的网络默认为桥接网络,都会桥接到docker0上。
[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229098fdd no

[root@localhost ~]# docker run -d nginx #运行一个nginx容器
c760a1b6c9891c02c992972d10a99639d4816c4160d633f1c5076292855bbf2b

[root@localhost ~]# brctl show
bridge name bridge id STP enabled interfaces
docker0 8000.024229098fdd no veth3f1b114

一个新的网络接口veth3f1b114桥接到了docker0上,veth3f1b114就是新创建的容器的虚拟网卡。进入容器查看其网络配置:
[root@localhost ~]# docker exec -it c760a1b6c98 bash
root@c760a1b6c989:/# apt-get update
root@c760a1b6c989:/# apt-get iproute
root@c760a1b6c989:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
38: eth0@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

从上可以看到容器内有一个网卡eth0@if39,实际上eth0@if39veth3f1b114是一对veth pairveth pair是一种成对出现的特殊网络设备,可以想象它们由一根虚拟的网线进行连接的一对网卡,eth0@if39在容器中,veth3f1b114挂在网桥docker0上,最终的效果就是eth0@if39也挂在了docker0上

桥接式网络是目前较为流行和默认的解决方案。但是这种方案的弊端是无法跨主机通信的,仅能在宿主机本地进行,而解决该问题的方法就是NAT。所有接入到该桥接设备上的容器都会被NAT隐藏,它们发往Docker主机外部的所有流量都会经过源地址转换后发出,并且默认是无法直接接受节点之外的其他主机发来的请求。当需要接入Docker主机外部流量,就需要进行目标地址转换甚至端口转换将其暴露在外部网络当中。如下图:

img

容器内的属于私有地址,需要在左侧的主机上的eth0上进行源地址转换,而右侧的地址需要被访问,就需要将eth0的地址进行NAT转换。SNAT—->DNAT

这样的通信方式会比较麻烦,从而需要借助第三方的网络插件实现这样的跨主机通信的网络策略。

1.2、Kubernetes网络模型

我们知道的是,在K8S上的网络通信包含以下几类:

  • 容器间的通信:同一个Pod内的多个容器间的通信,它们之间通过lo网卡进行通信。

  • Pod之间的通信:通过Pod IP地址进行通信。

  • Pod和Service之间的通信:Pod IP地址和Service IP进行通信,两者并不属于同一网络,实现方式是通过IPVS或iptables规则转发。

  • Service和集群外部客户端的通信,实现方式:Ingress、NodePort、Loadbalance

    K8S网络的实现不是集群内部自己实现,而是依赖于第三方网络插件—-CNI(Container Network Interface)

    flannel、calico、canel等是目前比较流行的第三方网络插件。

    这三种的网络插件需要实现Pod网络方案的方式通常有以下几种:

虚拟网桥、多路复用(MacVLAN)、硬件交换(SR-IOV)

无论是上面的哪种方式在容器当中实现,都需要大量的操作步骤,而K8S支持CNI插件进行编排网络,以实现Pod和集群网络管理功能的自动化。每次Pod被初始化或删除,kubelet都会调用默认的CNI插件去创建一个虚拟设备接口附加到相关的底层网络,为Pod去配置IP地址、路由信息并映射到Pod对象的网络名称空间。

在配置Pod网络时,kubelet会在默认的/etc/cni/net.d/目录中去查找CNI JSON配置文件,然后通过type属性到/opt/cni/bin中查找相关的插件二进制文件,如下面的”portmap”。然后CNI插件调用IPAM插件(IP地址管理插件)来配置每个接口的IP地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master ~]# cat /etc/cni/net.d/10-flannel.conflist 
{
"name": "cbr0",
"plugins": [
{
"type": "flannel",
"delegate": {
"hairpinMode": true,
"isDefaultGateway": true
}
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}

kubelet调用第三方插件,进行网络地址的分配

CNI主要是定义容器网络模型规范,链接容器管理系统和网络插件,两者主要通过上面的JSON格式文件进行通信,实现容器的网络功能。CNI的主要核心是:在创建容器时,先创建好网络名称空间(netns),然后调用CNI插件为这个netns配置网络,最后在启动容器内的进程。

常见的CNI网络插件包含以下几种:

  • Flannel:为Kubernetes提供叠加网络的网络插件,基于TUN/TAP隧道技术,使用UDP封装IP报文进行创建叠 加网络,借助etcd维护网络的分配情况,缺点:无法支持网络策略访问控制。
  • Calico:基于BGP的三层网络插件,也支持网络策略进而实现网络的访问控制;它在每台主机上都运行一个虚拟路由,利用Linux内核转发网络数据包,并借助iptables实现防火墙功能。实际上Calico最后的实现就是将每台主机都变成了一台路由器,将各个网络进行连接起来,实现跨主机通信的功能。
  • Canal:由Flannel和Calico联合发布的一个统一网络插件,提供CNI网络插件,并支持网络策略实现。
  • 其他的还包括Weave Net、Contiv、OpenContrail、Romana、NSX-T、kube-router等等。而Flannel和Calico是目前最流行的选择方案。

1.3、Flannel网络插件

在各节点上的Docker主机在docker0上默认使用同一个子网,不同节点的容器都有可能会获取到相同的地址,那么在跨节点通信时就会出现地址冲突的问题。并且在多个节点上的docker0使用不同的子网,也会因为没有准确的路由信息导致无法准确送达报文。

而为了解决这一问题,Flannel的解决办法是,预留一个使用网络,如10.244.0.0/16,然后自动为每个节点的Docker容器引擎分配一个子网,如10.244.1.0/24和10.244.2.0/24,并将分配信息保存在etcd持久存储。

第二个问题的解决,Flannel是采用不同类型的后端网络模型进行处理。其后端的类型有以下几种:

  • VxLAN:使用内核中的VxLAN模块进行封装报文。也是flannel推荐的方式,其报文格式如下:

img

  • host-gw:即Host GateWay,通过在节点上创建目标容器地址的路由直接完成报文转发,要求各节点必须在同一个2层网络,对报文转发性能要求较高的场景使用。
  • UDP:使用普通的UDP报文封装完成隧道转发。

1.4、VxLAN后端和direct routing

VxLAN(Virtual extensible Local Area Network)虚拟可扩展局域网,采用MAC in UDP封装方式,具体的实现方式为:

  • 1、将虚拟网络的数据帧添加到VxLAN首部,封装在物理网络的UDP报文中
  • 2、以传统网络的通信方式传送该UDP报文
  • 3、到达目的主机后,去掉物理网络报文的头部信息以及VxLAN首部,并交付给目的终端

跨节点的Pod之间的通信就是以上的一个过程,整个过程中通信双方对物理网络是没有感知的。如下网络图:

img

VxLAN的部署可以直接在官方上找到其YAML文件,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master:~# kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/bc79dd1505b0c8681ece4de4c0d86c5cd2643275/Documentation/kube-flannel.yml
clusterrole.rbac.authorization.k8s.io/flannel created
clusterrolebinding.rbac.authorization.k8s.io/flannel created
serviceaccount/flannel created
configmap/kube-flannel-cfg created
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created

#输出如下结果表示flannel可以正常运行了
[root@k8s-master ~]# kubectl get daemonset -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
kube-flannel-ds 3 3 3 3 3 beta.kubernetes.io/arch=amd64 202d
kube-proxy 3 3 3 3 3 beta.kubernetes.io/arch=amd64 202d

运行正常后,flanneld会在宿主机的/etc/cni/net.d目录下生成自已的配置文件,kubelet将会调用它。

网络插件运行成功后,Node状态才Ready。

1
2
3
4
5
[root@k8s-master ~]# kubectl get node
NAME STATUS ROLES AGE VERSION
k8s-master Ready master 202d v1.11.2
k8s-node01 Ready <none> 202d v1.11.2
k8s-node02 Ready <none> 201d v1.11.2

flannel运行后,在各Node宿主机多了一个网络接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#master节点的flannel.1网络接口,其网段为:10.244.0.0
[root@k8s-master ~]# ifconfig flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::31:5dff:fe01:4bc0 prefixlen 64 scopeid 0x20<link>
ether 02:31:5d:01:4b:c0 txqueuelen 0 (Ethernet)
RX packets 1659239 bytes 151803796 (144.7 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2115439 bytes 6859187242 (6.3 GiB)
TX errors 0 dropped 10 overruns 0 carrier 0 collisions 0

#node1节点的flannel.1网络接口,其网段为:10.244.1.0
[root@k8s-node01 ~]# ifconfig flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.1.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::2806:4ff:fe71:2253 prefixlen 64 scopeid 0x20<link>
ether 2a:06:04:71:22:53 txqueuelen 0 (Ethernet)
RX packets 136904 bytes 16191144 (15.4 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 180775 bytes 512637365 (488.8 MiB)
TX errors 0 dropped 8 overruns 0 carrier 0 collisions 0

#node2节点的flannel.1网络接口,其网段为:10.244.2.0
[root@k8s-node02 ~]# ifconfig flannel.1
flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.2.0 netmask 255.255.255.255 broadcast 0.0.0.0
inet6 fe80::58b7:7aff:fe8d:2d prefixlen 64 scopeid 0x20<link>
ether 5a:b7:7a:8d:00:2d txqueuelen 0 (Ethernet)
RX packets 9847824 bytes 12463823121 (11.6 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 2108796 bytes 185073173 (176.4 MiB)
TX errors 0 dropped 13 overruns 0 carrier 0 collisions 0

从上面的结果可以知道 :

  1. flannel默认就是VXLAN模式,即Overlay Network。
  2. flanneld创建了一个flannel.1接口,它是专门用来封装隧道协议的,默认分给集群的Pod网段为10.244.0.0/16。
  3. flannel给k8s-master节点配置的Pod网络为10.244.0.0段,给k8s-node01节点配置的Pod网络为10.244.1.0段,给k8s-node02节点配置的Pod网络为10.244.2.0段,如果有更多的节点,以此类推。

举个实际例子

1
2
3
4
5
6
7
8
9
#启动一个nginx容器,副本为3
[root@k8s-master ~]# kubectl run nginx --image=nginx:1.14 --port=80 --replicas=3
deployment.apps/nginx created

#查看Pod
[root@k8s-master ~]# kubectl get pods -o wide |grep nginx
nginx-5bd76bcc4f-8s64s 1/1 Running 0 2m 10.244.2.85 k8s-node02
nginx-5bd76bcc4f-mr6k5 1/1 Running 0 2m 10.244.1.146 k8s-node01
nginx-5bd76bcc4f-pb257 1/1 Running 0 2m 10.244.0.17 k8s-master

可以看到,3个Pod都分别运行在各个节点之上,其中master上的Pod的ip为:10.244.0.17,在master节点上查看网络接口可以发现在各个节点上多了一个虚拟接口cni0,其ip地址为10.244.0.1。它是由flanneld创建的一个虚拟网桥叫cni0,在Pod本地通信使用。 这里需要注意的是,cni0虚拟网桥,仅作用于本地通信!!!!

1
2
3
4
5
6
7
8
9
[root@k8s-master ~]# ifconfig cni0
cni0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450
inet 10.244.0.1 netmask 255.255.255.0 broadcast 0.0.0.0
inet6 fe80::848a:beff:fe44:4959 prefixlen 64 scopeid 0x20<link>
ether 0a:58:0a:f4:00:01 txqueuelen 1000 (Ethernet)
RX packets 2772994 bytes 300522237 (286.6 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3180562 bytes 928687288 (885.6 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

flanneld为每个Pod创建一对veth虚拟设备,一端放在容器接口上,一端放在cni0桥上。 使用brctl查看该网桥:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#可以看到有一veth的网络接口桥接在cni0网桥上
[root@k8s-master ~]# brctl show cni0
bridge name bridge id STP enabled interfaces
cni0 8000.0a580af40001 no veth020fafae


#宿主机ping测试访问Pod ip
[root@k8s-master ~]# ping 10.244.0.17
PING 10.244.0.17 (10.244.0.17) 56(84) bytes of data.
64 bytes from 10.244.0.17: icmp_seq=1 ttl=64 time=0.291 ms
64 bytes from 10.244.0.17: icmp_seq=2 ttl=64 time=0.081 ms
^C
--- 10.244.0.17 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3000ms
rtt min/avg/max/mdev = 0.055/0.129/0.291/0.094 ms

在现有的Flannel VxLAN网络中,两台主机上的Pod间通信,也是正常的,如master节点上的Pod访问node01上的Pod:

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master ~]# kubectl exec -it nginx-5bd76bcc4f-pb257 -- /bin/bash
root@nginx-5bd76bcc4f-pb257:/# ping 10.244.1.146
PING 10.244.1.146 (10.244.1.146) 56(84) bytes of data.
64 bytes from 10.244.1.146: icmp_seq=1 ttl=62 time=1.44 ms
64 bytes from 10.244.1.146: icmp_seq=2 ttl=62 time=0.713 ms
64 bytes from 10.244.1.146: icmp_seq=3 ttl=62 time=0.713 ms
64 bytes from 10.244.1.146: icmp_seq=4 ttl=62 time=0.558 ms
^C
--- 10.244.1.146 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 0.558/0.858/1.448/0.346 ms

可以看到容器跨主机是可以正常通信的,那么容器的跨主机通信是如何实现的呢?????master上查看路由表信息:

1
2
3
4
5
[root@k8s-master ~]# ip route
......
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink
10.244.2.0/24 via 10.244.2.0 dev flannel.1 onlink
......

发送到10.244.1.0/2410.244.20/24网段的数据报文发给本机的flannel.1接口,即进入二层隧道,然后对数据报文进行封装(封装VxLAN首部–>UDP首部–>IP首部–>以太网首部),到达目标Node节点后,由目标Node上的flannel.1进行解封装。使用tcpdump进行 抓一下包,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#在宿主机和容器内都进行ping另外一台主机上的Pod ip并进行抓包
[root@k8s-master ~]# ping -c 10 10.244.1.146
[root@k8s-master ~]# kubectl exec -it nginx-5bd76bcc4f-pb257 -- /bin/bash
root@nginx-5bd76bcc4f-pb257:/# ping 10.244.1.146

[root@k8s-master ~]# tcpdump -i flannel.1 -nn host 10.244.1.146
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes

#宿主机ping后抓包情况如下:
22:22:35.737977 IP 10.244.0.0 > 10.244.1.146: ICMP echo request, id 29493, seq 1, length 64
22:22:35.738902 IP 10.244.1.146 > 10.244.0.0: ICMP echo reply, id 29493, seq 1, length 64
22:22:36.739042 IP 10.244.0.0 > 10.244.1.146: ICMP echo request, id 29493, seq 2, length 64
22:22:36.739789 IP 10.244.1.146 > 10.244.0.0: ICMP echo reply, id 29493, seq 2, length 64

#容器ping后抓包情况如下:
22:33:49.295137 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 1, length 64
22:33:49.295933 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 1, length 64
22:33:50.296736 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 2, length 64
22:33:50.297222 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 2, length 64
22:33:51.297556 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 837, seq 3, length 64
22:33:51.298054 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 837, seq 3, length 64

可以看到报文都是经过flannel.1网络接口进入2层隧道进而转发

VXLAN是Linux内核本身支持的一种网络虚拟化技术,是内核的一个模块,在内核态实现封装解封装,构建出覆盖网络,其实就是一个由各宿主机上的Flannel.1设备组成的虚拟二层网络。

由于VXLAN由于额外的封包解包,导致其性能较差,所以Flannel就有了host-gw模式,即把宿主机当作网关,除了本地路由之外没有额外开销,性能和calico差不多,由于没有叠加来实现报文转发,这样会导致路由表庞大。因为一个节点对应一个网络,也就对应一条路由条目。

host-gw虽然VXLAN网络性能要强很多。,但这种方式有个缺陷:要求各物理节点必须在同一个二层网络中。物理节点必须在同一网段中。这样会使得一个网段中的主机量会非常多,万一发一个广播报文就会产生干扰。在私有云场景下,宿主机不在同一网段是很常见的状态,所以就不能使用host-gw了。

VXLAN还有另外一种功能,VXLAN也支持类似host-gw的玩法,如果两个节点在同一网段时使用host-gw通信,如果不在同一网段中,即 当前pod所在节点与目标pod所在节点中间有路由器,就使用VXLAN这种方式,使用叠加网络。 结合了Host-gw和VXLAN,这就是VXLAN的Direct routing模式

Flannel VxLAN的Direct routing模式配置

修改kube-flannel.yml文件,将flannel的configmap对象改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@k8s-master ~]# vim kube-flannel.yml 
......
net-conf.json: |
{
"Network": "10.244.0.0/16", #默认网段
"Backend": {
"Type": "vxlan",
"Directrouting": true #增加
}
}
......

[root@k8s-master ~]# kubectl apply -f kube-flannel.yml
clusterrole.rbac.authorization.k8s.io/flannel configured
clusterrolebinding.rbac.authorization.k8s.io/flannel configured
serviceaccount/flannel unchanged
configmap/kube-flannel-cfg configured
daemonset.extensions/kube-flannel-ds-amd64 created
daemonset.extensions/kube-flannel-ds-arm64 created
daemonset.extensions/kube-flannel-ds-arm created
daemonset.extensions/kube-flannel-ds-ppc64le created
daemonset.extensions/kube-flannel-ds-s390x created

#查看路由信息
[root@k8s-master ~]# ip route
......
10.244.1.0/24 via 192.168.56.12 dev eth0
10.244.2.0/24 via 192.168.56.13 dev eth0
......

从上面的结果可以看到,发往10.244.1.0/2410.244.1.0/24的包都是直接经过eth0网络接口直接发出去的,这就是Directrouting。如果两个节点是跨网段的,则flannel自动降级为VxLAN模式。

此时,在各 个 集群节点上执行“iptables -nL”命令 可以 看到, iptables filter 表 的 FORWARD 链 上 由其 生成 了 如下 两条 转发 规则, 它 显 式 放行 了10. 244. 0. 0/ 16 网络 进出 的 所有 报文, 用于 确保 由 物理 接口 接收 或 发送 的 目标 地址 或 源 地址 为10. 244. 0. 0/ 16网络 的 所有 报文 均能 够 正常 通行。 这些 是 Direct Routing 模式 得以 实现 的 必要条件:

1
2
3
target 		prot 	opt 	source 				destination 
ACCEPT all -- 10. 244. 0. 0/ 16 0. 0. 0. 0/ 0
ACCEPT all -- 0. 0. 0. 0/ 0 10. 244. 0. 0/ 16

再在此之前创建的Pod和宿主机上进行ping测试,可以看到在flannel.1接口上已经抓不到包了,在eth0上可以用抓到ICMP的包,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master ~]# tcpdump -i flannel.1 -nn host 10.244.1.146
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on flannel.1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel

[root@k8s-master ~]# tcpdump -i eth0 -nn host 10.244.1.146
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
22:48:52.376393 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 839, seq 1, length 64
22:48:52.376877 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 839, seq 1, length 64
22:48:53.377005 IP 10.244.0.17 > 10.244.1.146: ICMP echo request, id 839, seq 2, length 64
22:48:53.377621 IP 10.244.1.146 > 10.244.0.17: ICMP echo reply, id 839, seq 2, length 64

22:50:28.647490 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 46141, seq 1, length 64
22:50:28.648320 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 46141, seq 1, length 64
22:50:29.648958 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 46141, seq 2, length 64
22:50:29.649380 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 46141, seq 2, length 64

1.5、Host-gw后端

Flannel除了上面2种数据传输的方式以外,还有一种是host-gw的方式,host-gw后端是通过添加必要的路由信息使用节点的二层网络直接发送Pod的通信报文。它的工作方式类似于Directrouting的功能,但是其并不具备VxLan的隧道转发能力。

编辑kube-flannel的配置清单,将ConfigMap资源kube-flannel-cfg的data字段中网络配置进行修改,如下:

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master ~]# vim kube-flannel.yml 
......
net-conf.json: |
{
"Network": "10.244.0.0/16",
"Backend": {
"Type": "host-gw"
}
}
......

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

配置完成后,各节点会生成类似directrouting一样的 路由和iptables规则,用于实现二层转发Pod网络的通信报文,省去了隧道转发模式的额外开销。但是存在的问题点是,对于不在同一个二层网络的报文转发,host-gw是无法实现的。延续上面的例子,进行抓包查看:

master上的Pod:10.244.0.17向node01节点上的Pod:10.244.1.146发送ICMP报文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#查看路由表信息,可以看到其报文的发送方向都是和Directrouting是一样的
[root@k8s-master ~]# ip route
......
10.244.1.0/24 via 192.168.56.12 dev eth0
10.244.2.0/24 via 192.168.56.13 dev eth0
.....

#进行ping包测试
[root@k8s-master ~]# ping -c 2 10.244.1.146

#在eth0上进行抓包
[root@k8s-master ~]# tcpdump -i eth0 -nn host 10.244.1.146
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
23:11:05.556972 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 59528, seq 1, length 64
23:11:05.557794 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 59528, seq 1, length 64
23:11:06.558231 IP 192.168.56.11 > 10.244.1.146: ICMP echo request, id 59528, seq 2, length 64
23:11:06.558610 IP 10.244.1.146 > 192.168.56.11: ICMP echo reply, id 59528, seq 2, length 64

该模式下,报文转发的相关流程如下:

  • 1、Pod(10.244.0.17)向Pod(10.244.1.146)发送报文,查看到报文中的目的地址为:10.244.1.146,并非本地网段,会直接发送到网关(192.168.56.11);

  • 2、网关发现该目标地址为10.244.1.146,要到达10.244.1.0/24网段,需要送达到node2 的物理网卡,node2接收以后发现该报文的目标地址属于本机上的另一个虚拟网卡,然后转发到相对应的Pod(10.244.1.146)

    工作模式流程图如下:

img

以上就是Flannel网络模型的三种工作模式,但是flannel自身并不具备为Pod网络实现网络策略和网络通信隔离的功能,为此只能借助于Calico联合统一的项目Calnal项目进行构建网络策略的功能。

2、网络策略

网络策略(Network Policy )是 Kubernetes 的一种资源。Network Policy 通过 Label 选择 Pod,并指定其他 Pod 或外界如何与这些 Pod 通信。

Pod的网络流量包含流入(Ingress)和流出(Egress)两种方向。默认情况下,所有 Pod 是非隔离的,即任何来源的网络流量都能够访问 Pod,没有任何限制。当为 Pod 定义了 Network Policy,只有 Policy 允许的流量才能访问 Pod。

Kubernetes的网络策略功能也是由第三方的网络插件实现的,因此,只有支持网络策略功能的网络插件才能进行配置网络策略,比如Calico、Canal、kube-router等等。

PS:Kubernetes自1.8版本才支持Egress网络策略,在该版本之前仅支持Ingress网络策略。

2.1、部署Canal提供网络策略功能

calico官网

Calico可以独立地为Kubernetes提供网络解决方案和网络策略,也可以和flannel相结合,由flannel提供网络解决方案,Calico仅用于提供网络策略,此时将Calico称为Canal。结合flannel工作时,Calico提供的默认配置清单式以flannel默认使用的10.244.0.0/16为Pod网络,因此在集群中kube-controller-manager启动时就需要通过–cluster-cidr选项进行设置使用该网络地址,并且—allocate-node-cidrs的值应设置为true。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#设置RBAC
[root@k8s-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/rbac.yaml

#部署Canal提供网络策略
[root@k8s-master ~]# kubectl apply -f https://docs.projectcalico.org/v3.2/getting-started/kubernetes/installation/hosted/canal/canal.yaml

[root@k8s-master ~]# kubectl get ds canal -n kube-system
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
canal 3 3 0 3 0 beta.kubernetes.io/os=linux 2m

部署canal需要的镜像,建议先拉取镜像,避免耗死资源:
quay.io/calico/node:v3.2.6
quay.io/calico/cni:v3.2.6
quay.io/coreos/flannel:v0.9.1

[root@k8s-master ~]# kubectl get pods -n kube-system -o wide |grep canal
canal-2hqwt 3/3 Running 0 1h 192.168.56.11 k8s-master
canal-c5pxr 3/3 Running 0 1h 192.168.56.13 k8s-node02
canal-kr662 3/3 Running 6 1h 192.168.56.12 k8s-node01

Canal作为DaemonSet部署到每个节点,属于kube-system这个名称空间。需要注意的是,Canal只是直接使用了Calicoflannel项目,代码本身没有修改,Canal只是一种部署的模式,用于安装和配置项目。

2.2、配置网络策略

在Kubernetes系统中,报文的流入和流出的核心组件是Pod资源,它们也是网络策略功能的主要应用对象。NetworkPolicy对象通过podSelector选择 一组Pod资源作为控制对象。NetworkPolicy是定义在一组Pod资源之上用于管理入站流量,或出站流量的一组规则,有可以是出入站规则一起生效,规则的生效模式通常由spec.policyTypes进行 定义。如下图:

img

默认情况下,Pod对象的流量控制是为空的,报文可以自由出入。在附加网络策略之后,Pod对象会因为NetworkPolicy而被隔离,一旦名称空间中有任何NetworkPolicy对象匹配了某特定的Pod对象,则该Pod将拒绝NetworkPolicy规则中不允许的所有连接请求,但是那些未被匹配到的Pod对象依旧可以接受所有流量。

就特定的Pod集合来说,入站和出站流量默认是放行状态,除非有规则可以进行匹配。还有一点需要注意的是,在spec.policyTypes中指定了生效的规则类型,但是在networkpolicy.spec字段中嵌套定义了没有任何规则的Ingress或Egress时,则表示拒绝入站或出站的一切流量。定义网络策略的基本格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
apiVersion: networking.k8s.io/v1		#定义API版本
kind: NetworkPolicy #定义资源类型
metadata:
name: allow-myapp-ingress #定义NetwokPolicy的名字
namespace: default
spec: #NetworkPolicy规则定义
podSelector: #匹配拥有标签app:myapp的Pod资源
matchLabels:
app: myapp
policyTypes ["Ingress"] #NetworkPolicy类型,可以是Ingress,Egress,或者两者共存
ingress: #定义入站规则
- from:
- ipBlock: #定义可以访问的网段
cidr: 10.244.0.0/16
except: #排除的网段
- 10.244.3.0/24
- podSelector: #选定当前default名称空间,标签为app:myapp可以入站
matchLabels:
app: myapp
ports: #开放的协议和端口定义
- protocol: TCP
port: 80

该网络策略就是将default名称空间中拥有标签"app=myapp"的Pod资源开放80/TCP端口给10.244.0.0/16网段,并排除10.244.3.0/24网段的访问,并且也开放给标签为app=myapp的所有Pod资源进行访问。

为了看出Network Policy的效果,先部署一个httpd的应用。配置清单文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master ~]# mkdir network-policy-demo
[root@k8s-master ~]# cd network-policy-demo/
[root@k8s-master network-policy-demo]# vim httpd.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
name: httpd
spec:
replicas: 3
template:
metadata:
labels:
run: httpd
spec:
containers:
- name: httpd
image: httpd:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80

---
apiVersion: v1
kind: Service
metadata:
name: httpd-svc
spec:
type: NodePort
selector:
run: httpd
ports:
- protocol: TCP
nodePort: 30000
port: 8080
targetPort: 80

创建三个副本,通过NodePort类型的Service对外方服务,部署应用:

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master network-policy-demo]# kubectl apply -f httpd.yaml 
deployment.apps/httpd unchanged
service/httpd-svc created
[root@k8s-master network-policy-demo]# kubectl get pods -o wide |grep httpd
httpd-75f655479d-882hz 1/1 Running 0 4m 10.244.0.2 k8s-master
httpd-75f655479d-h7lrr 1/1 Running 0 4m 10.244.2.2 k8s-node02
httpd-75f655479d-kzr5g 1/1 Running 0 4m 10.244.1.2 k8s-node01

[root@k8s-master network-policy-demo]# kubectl get svc httpd-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
httpd-svc NodePort 10.99.222.179 <none> 8080:30000/TCP 4m

当前没有定义任何Network Policy,验证应用的访问:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#启动一个busybox Pod,可以访问Service,也可以ping副本的Pod

[root@k8s-master ~]# kubectl run busybox --rm -it --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.222.179:8080)
index.html 100% |*********************************************************************************************| 45 0:00:00 ETA
/ # ping -c 2 10.244.1.2
PING 10.244.1.2 (10.244.1.2): 56 data bytes
64 bytes from 10.244.1.2: seq=0 ttl=63 time=0.507 ms
64 bytes from 10.244.1.2: seq=1 ttl=63 time=0.228 ms

--- 10.244.1.2 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.228/0.367/0.507 ms


#集群节点也可以访问Sevice和ping通副本Pod
[root@k8s-node01 ~]# curl 10.99.222.179:8080
<html><body><h1>It works!</h1></body></html>
[root@k8s-node01 ~]# ping -c 2 10.244.2.2
PING 10.244.2.2 (10.244.2.2) 56(84) bytes of data.
64 bytes from 10.244.2.2: icmp_seq=1 ttl=63 time=0.931 ms
64 bytes from 10.244.2.2: icmp_seq=2 ttl=63 time=0.812 ms

--- 10.244.2.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 0.812/0.871/0.931/0.066 ms

#集群外部访问192.168.56.11:30000也是通的
[root@localhost ~]# curl 192.168.56.11:30000
<html><body><h1>It works!</h1></body></html>

那么下面再去设置不同的Network Policy来管控Pod的访问。

2.3、管控入站流量

NetworkPolicy资源属于名称空间级别,它的作用范围为其所属的名称空间。

1、设置默认的Ingress策略

用户可以创建一个NetworkPolicy来为名称空间设置一个默认的隔离策略,该策略选择所有的Pod对象,然后允许或拒绝任何到达这些Pod的入站流量,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master network-policy-demo]# vim policy-demo.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {}
policyTypes: ["Ingress"]
#指明了Ingress生效规则,但不定义任何Ingress字段,因此不能匹配任何源端点,从而拒绝所有的入站流量

[root@k8s-master network-policy-demo]# kubectl apply -f policy-demo.yaml
networkpolicy.networking.k8s.io/deny-all-ingress created
[root@k8s-master network-policy-demo]# kubectl get networkpolicy
NAME POD-SELECTOR AGE
deny-all-ingress <none> 11s

#此时再去访问测试,是无法ping通,无法访问的
[root@k8s-master ~]# kubectl run busybox --rm -it --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.222.179:8080)
wget: can't connect to remote host (10.99.222.179): Connection timed out

如果要将默认策略设置为允许所有入站流量,只需要定义Ingress字段,并将这个字段设置为空,以匹配所有源端点,但本身不设定网络策略,就已经是默认允许所有入站流量访问的,下面给出一个定义的格式:

1
2
3
4
5
6
7
8
9
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {}
policyTypes: ["Ingress"]
ingress:
- {}

实践中,通常将默认的网络策略设置为拒绝所有入站流量,然后再放行允许的源端点的入站流量。

2、放行特定的入站流量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@k8s-master network-policy-demo]# vim policy-demo.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: access-httpd
spec:
podSelector:
matchLabels:
run: httpd
policyTypes: ["Ingress"]
ingress:
- from:
- ipBlock:
cidr: 10.244.0.0/16
except:
- 10.244.2.0/24
- 10.244.1.0/24
- podSelector:
matchLabels:
access: "true"
ports:
- protocol: TCP
port: 80

[root@k8s-master network-policy-demo]# kubectl apply -f policy-demo.yaml
networkpolicy.networking.k8s.io/access-httpd created
[root@k8s-master network-policy-demo]# kubectl get networkpolicy
NAME POD-SELECTOR AGE
access-httpd run=httpd 6s

验证NetworkPolicy的有效性:

1
2
3
4
5
6
7
8
9
10
11
#创建带有标签的busybox pod访问,是可以正常访问的,但是因为仅开放了TCP协议,所以PING是无法ping通的
[root@k8s-master ~]# kubectl run busybox --rm -it --labels="access=true" --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.222.179:8080)
index.html 100% |*********************************************************************************************| 45 0:00:00 ETA
/ # ping -c 3 10.244.0.2
PING 10.244.0.2 (10.244.0.2): 56 data bytes

--- 10.244.0.2 ping statistics ---
3 packets transmitted, 0 packets received, 100% packet loss

2.4、管控出站流量

通常,出站的流量默认策略应该是允许通过的,但是当有精细化需求,仅放行那些有对外请求需要的Pod对象的出站流量,也可以先为名称空间设置“禁止所有”的默认策略,再细化制定准许的策略。networkpolicy.spec中嵌套的Egress字段用来定义出站流量规则。

1、设定默认Egress策略

和Igress一样,只需要通过policyTypes字段指明生效的Egress类型规则,然后不去定义Egress字段,就不会去匹配到任何目标端点,从而拒绝所有的出站流量。

1
2
3
4
5
6
7
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-egress
spec:
podSelector: {}
policyTypes: ["Egress"]

实践中,需要进行严格隔离的环境通常将默认的策略设置为拒绝所有出站流量,再去细化配置允许到达的目标端点的出站流量。

2、放行特定的出站流量

下面举个例子定义一个Egress规则,对标签run=httpd的Pod对象,到达标签为access=true的Pod对象的80端口的流量进行放行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master network-policy-demo]# vim egress-policy.yaml 
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: httpd-egress
spec:
podSelector:
matchLabels:
run: httpd
policyTypes: ["Egress"]
egress:
- to:
- podSelector:
matchLabels:
access: "true"
ports:
- protocol: TCP
port: 80


#NetworkPolicy检测,一个带有access=true标签,一个不带
[root@k8s-master ~]# kubectl run busybox --rm -it --labels="access=true" --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.222.179:8080)
index.html 100% |*********************************************************************************************| 45 0:00:00 ETA
/ # exit
Session ended, resume using 'kubectl attach busybox-686cb649b6-6j4qx -c busybox -i -t' command when the pod is running
deployment.apps "busybox" deleted

[root@k8s-master ~]# kubectl run busybox2 --rm -it --image=busybox /bin/sh
If you don't see a command prompt, try pressing enter.
/ # wget httpd-svc:8080
Connecting to httpd-svc:8080 (10.99.222.179:8080)
wget: can't connect to remote host (10.99.222.179): Connection timed out

从上面的检测结果可以看到,带有标签access=true的Pod才能访问到httpd-svc,说明上面配置的Network Policy已经生效。

2.5、隔离名称空间

实践中,通常需要彼此隔离所有的名称空间,但是又需要允许它们可以和kube-system名称空间中的Pod资源进行流量交换,以实现监控和名称解析等各种管理功能。下面的配置清单示例在default名称空间定义相关规则,在出站和入站都默认均为拒绝的情况下,它用于放行名称空间内部的各Pod对象之间的通信,以及和kube-system名称空间内各Pod间的通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-deny-all
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: namespace-allow
namespace: default
spec:
policyTypes: ["Ingress","Egress"]
podSelector: {}
ingress:
- from:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]
egress:
- to:
- namespaceSelector:
matchExpressions:
- key: name
operator: In
values: ["default","kube-system"]

需要注意的是,有一些额外的系统附件可能会单独部署到独有的名称空间中,比如将prometheus监控系统部署到prom名称空间等,这类具有管理功能的附件所在的名称空间和每一个特定的名称空间的出入流量也是需要被放行的。

k8s学习笔记-19-Kubernetes dashboard认证访问

[TOC]

Dashboard:https://github.com/kubernetes/dashboard

一、Dashboard部署

由于需要用到k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0,这里有2种方式进行pull 镜像。docker search该镜像名称,直接pull,再重新进行tag;另外一种方式是通过谷歌容器镜像拉取。

1
2
3
4
5
[root@k8s-node01 ~]# docker pull siriuszg/kubernetes-dashboard-amd64  
[root@k8s-node01 ~]# docker tag siriuszg/kubernetes-dashboard-amd64:latest k8s.gcr.io/kubernetes-dashboard-amd64:v1.10.0

或者是
[root@k8s-node01 ~]# docker pull mirrorgooglecontainers/kubernetes-dashboard-amd64:v1.10.0

再看其部署的过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master ~]# kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
secret/kubernetes-dashboard-certs created
serviceaccount/kubernetes-dashboard created
role.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard-minimal created
deployment.apps/kubernetes-dashboard created
service/kubernetes-dashboard created

[root@k8s-master ~]# kubectl get pods -n kube-system
NAME READY STATUS RESTARTS AGE
coredns-78fcdf6894-nmcmz 1/1 Running 1 54d
coredns-78fcdf6894-p5pfm 1/1 Running 1 54d
etcd-k8s-master 1/1 Running 2 54d
kube-apiserver-k8s-master 1/1 Running 9 54d
kube-controller-manager-k8s-master 1/1 Running 5 54d
kube-flannel-ds-n5c86 1/1 Running 1 54d
kube-flannel-ds-nrcw2 1/1 Running 1 52d
kube-flannel-ds-pgpr7 1/1 Running 5 54d
kube-proxy-glzth 1/1 Running 1 52d
kube-proxy-rxlt7 1/1 Running 2 54d
kube-proxy-vxckf 1/1 Running 4 54d
kube-scheduler-k8s-master 1/1 Running 3 54d
kubernetes-dashboard-767dc7d4d-n4clq 1/1 Running 0 3s

[root@k8s-master ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 54d
kubernetes-dashboard ClusterIP 10.105.204.4 <none> 443/TCP 30m

[root@k8s-master ~]# kubectl patch svc kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}' -n kube-system #以打补丁方式修改dasboard的访问方式
service/kubernetes-dashboard patched
[root@k8s-master ~]# kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP 54d
kubernetes-dashboard NodePort 10.105.204.4 <none> 443:32645/TCP 31m

浏览器访问:https://192.168.56.12:32645,如图:**这里需要注意的是谷歌浏览器会禁止不安全证书访问,建议使用火狐浏览器,并且需要在高级选项中添加信任**

img

在k8s中 dashboard可以有两种访问方式:kubeconfig(HTTPS)和token(http)

1、token认证

(1)创建dashboard专用证书

1
2
3
4
5
[root@k8s-master pki]# (umask 077;openssl genrsa -out dashboard.key 2048)
Generating RSA private key, 2048 bit long modulus
.....................................................................................................+++
..........................+++
e is 65537 (0x10001)

(2)证书签署请求

1
2
3
4
5
[root@k8s-master pki]# openssl req -new -key dashboard.key -out dashboard.csr -subj "/O=magedu/CN=dashboard"
[root@k8s-master pki]# openssl x509 -req -in dashboard.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out dashboard.crt -days 365
Signature ok
subject=/O=magedu/CN=dashboard
Getting CA Private Key

(3)定义令牌方式仅能访问default名称空间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@k8s-master pki]# kubectl create secret generic dashboard-cert -n kube-system --from-file=./dashboard.crt --from-file=dashboard.key=./dashboard.key   #secret创建
secret/dashboard-cert created

[root@k8s-master pki]# kubectl get secret -n kube-system |grep dashboard
dashboard-cert Opaque 2 1m
kubernetes-dashboard-certs Opaque 0 3h
kubernetes-dashboard-key-holder Opaque 2 3h
kubernetes-dashboard-token-jpbgw kubernetes.io/service-account-token 3 3h

[root@k8s-master pki]# kubectl create serviceaccount def-ns-admin -n default #创建serviceaccount
serviceaccount/def-ns-admin created

[root@k8s-master pki]# kubectl create rolebinding def-ns-admin --clusterrole=admin --serviceaccount=default:def-ns-admin #service account账户绑定到集群角色admin
rolebinding.rbac.authorization.k8s.io/def-ns-admin created

[root@k8s-master pki]# kubectl describe secret def-ns-admin-token-k9fz9 #查看def-ns-admin这个serviceaccount的token
Name: def-ns-admin-token-k9fz9
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name=def-ns-admin
kubernetes.io/service-account.uid=56ed901c-d042-11e8-801a-000c2972dc1f

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1025 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZi1ucy1hZG1pbi10b2tlbi1rOWZ6OSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWYtbnMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NmVkOTAxYy1kMDQyLTExZTgtODAxYS0wMDBjMjk3MmRjMWYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWYtbnMtYWRtaW4ifQ.QfB5RR19nBv4-kFYyzW5-2n5Ksg-kON8lU18-COLBNfObQTDHs926m4k9f_5bto4YGncYi7sV_3oEec8ouW1FRjJWfY677L1IqIlwcuqc-g0DUo21zkjY_s3Lv3JSb_AfXUbZ7VTeWOhvwonqfK8uriGO1-XET-RBk1CE4Go1sL7X5qDgPjNO1g85D9IbIZG64VygplT6yZNc-b7tLNn_O49STthy6J0jdNk8lYxjy6UJohoTicy2XkZMHp8bNPBj9RqGqMSnnJxny5WO3vHxYAodKx7h6w-PtuON84lICnhiJ06RzsWjZfdeaQYg4gCZmd2J6Hdq0_K32n3l3kFLg

将该token复制后,填入验证,要知道的是,该token认证仅可以查看default名称空间的内容,如下图:

img

2、kubeconfig认证

(1)配置def-ns-admin的集群信息

1
2
[root@k8s-master pki]# kubectl config set-cluster kubernetes --certificate-authority=./ca.crt --server="https://192.168.56.11:6443" --embed-certs=true --kubeconfig=/root/def-ns-admin.conf
Cluster "kubernetes" set.

(2)使用token写入集群验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@k8s-master ~]# kubectl config set-credentials -h  #认证的方式可以通过crt和key文件,也可以使用token进行配置,这里使用tonken

Usage:
kubectl config set-credentials NAME [--client-certificate=path/to/certfile] [--client-key=path/to/keyfile]
[--token=bearer_token] [--username=basic_user] [--password=basic_password] [--auth-provider=provider_name]
[--auth-provider-arg=key=value] [options]

[root@k8s-master pki]# kubectl describe secret def-ns-admin-token-k9fz9
Name: def-ns-admin-token-k9fz9
Namespace: default
Labels: <none>
Annotations: kubernetes.io/service-account.name=def-ns-admin
kubernetes.io/service-account.uid=56ed901c-d042-11e8-801a-000c2972dc1f

Type: kubernetes.io/service-account-token

Data
====
ca.crt: 1025 bytes
namespace: 7 bytes
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZi1ucy1hZG1pbi10b2tlbi1rOWZ6OSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWYtbnMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NmVkOTAxYy1kMDQyLTExZTgtODAxYS0wMDBjMjk3MmRjMWYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWYtbnMtYWRtaW4ifQ.QfB5RR19nBv4-kFYyzW5-2n5Ksg-kON8lU18-COLBNfObQTDHs926m4k9f_5bto4YGncYi7sV_3oEec8ouW1FRjJWfY677L1IqIlwcuqc-g0DUo21zkjY_s3Lv3JSb_AfXUbZ7VTeWOhvwonqfK8uriGO1-XET-RBk1CE4Go1sL7X5qDgPjNO1g85D9IbIZG64VygplT6yZNc-b7tLNn_O49STthy6J0jdNk8lYxjy6UJohoTicy2XkZMHp8bNPBj9RqGqMSnnJxny5WO3vHxYAodKx7h6w-PtuON84lICnhiJ06RzsWjZfdeaQYg4gCZmd2J6Hdq0_K32n3l3kFLg

这里的token是base64编码,此处需要进行解码操作
[root@k8s-master ~]# kubectl get secret def-ns-admin-token-k9fz9 -o jsonpath={.data.token} |base64 -d
eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZi1ucy1hZG1pbi10b2tlbi1rOWZ6OSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWYtbnMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NmVkOTAxYy1kMDQyLTExZTgtODAxYS0wMDBjMjk3MmRjMWYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWYtbnMtYWRtaW4ifQ.QfB5RR19nBv4-kFYyzW5-2n5Ksg-kON8lU18-COLBNfObQTDHs926m4k9f_5bto4YGncYi7sV_3oEec8ouW1FRjJWfY677L1IqIlwcuqc-g0DUo21zkjY_s3Lv3JSb_AfXUbZ7VTeWOhvwonqfK8uriGO1-XET-RBk1CE4Go1sL7X5qDgPjNO1g85D9IbIZG64VygplT6yZNc-b7tLNn_O49STthy6J0jdNk8lYxjy6UJohoTicy2XkZMHp8bNPBj9RqGqMSnnJxny5WO3vHxYAodKx7h6w-PtuON84lICnhiJ06RzsWjZfdeaQYg4gCZmd2J6Hdq0_K32n3l3kFLg

配置token信息
[root@k8s-master ~]# kubectl config set-credentials def-ns-admin --token=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZi1ucy1hZG1pbi10b2tlbi1rOWZ6OSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWYtbnMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NmVkOTAxYy1kMDQyLTExZTgtODAxYS0wMDBjMjk3MmRjMWYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWYtbnMtYWRtaW4ifQ.QfB5RR19nBv4-kFYyzW5-2n5Ksg-kON8lU18-COLBNfObQTDHs926m4k9f_5bto4YGncYi7sV_3oEec8ouW1FRjJWfY677L1IqIlwcuqc-g0DUo21zkjY_s3Lv3JSb_AfXUbZ7VTeWOhvwonqfK8uriGO1-XET-RBk1CE4Go1sL7X5qDgPjNO1g85D9IbIZG64VygplT6yZNc-b7tLNn_O49STthy6J0jdNk8lYxjy6UJohoTicy2XkZMHp8bNPBj9RqGqMSnnJxny5WO3vHxYAodKx7h6w-PtuON84lICnhiJ06RzsWjZfdeaQYg4gCZmd2J6Hdq0_K32n3l3kFLg --kubeconfig=/root/def-ns-admin.conf
User "def-ns-admin" set.

(3)配置上下文和当前上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@k8s-master ~]# kubectl config set-context def-ns-admin@kubernetes --cluster=kubernetes --user=def-ns-admin --kubeconfig=/root/def-ns-admin.conf 
Context "def-ns-admin@kubernetes" created.
[root@k8s-master ~]# kubectl config use-context def-ns-admin@kubernetes --kubeconfig=/root/def-ns-admin.conf
Switched to context "def-ns-admin@kubernetes".

[root@k8s-master ~]# kubectl config view --kubeconfig=/root/def-ns-admin.conf
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.56.11:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: def-ns-admin
name: def-ns-admin@kubernetes
current-context: def-ns-admin@kubernetes
kind: Config
preferences: {}
users:
- name: def-ns-admin
user:
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJkZWZhdWx0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZWNyZXQubmFtZSI6ImRlZi1ucy1hZG1pbi10b2tlbi1rOWZ6OSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJkZWYtbnMtYWRtaW4iLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI1NmVkOTAxYy1kMDQyLTExZTgtODAxYS0wMDBjMjk3MmRjMWYiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6ZGVmYXVsdDpkZWYtbnMtYWRtaW4ifQ.QfB5RR19nBv4-kFYyzW5-2n5Ksg-kON8lU18-COLBNfObQTDHs926m4k9f_5bto4YGncYi7sV_3oEec8ouW1FRjJWfY677L1IqIlwcuqc-g0DUo21zkjY_s3Lv3JSb_AfXUbZ7VTeWOhvwonqfK8uriGO1-XET-RBk1CE4Go1sL7X5qDgPjNO1g85D9IbIZG64VygplT6yZNc-b7tLNn_O49STthy6J0jdNk8lYxjy6UJohoTicy2XkZMHp8bNPBj9RqGqMSnnJxny5WO3vHxYAodKx7h6w-PtuON84lICnhiJ06RzsWjZfdeaQYg4gCZmd2J6Hdq0_K32n3l3kFLg

将/root/def-ns-admin.conf文件发送到宿主机,浏览器访问时选择Kubeconfig认证,载入该配置文件,点击登陆,即可实现访问,如图:

img

二、总结

1、部署dashboard:

1
kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

2、将Service改为Node Port方式进行访问:

1
kubectl patch svc kubernetes-dashboard -p '{"spec":{"type":"NodePort"}}' -n kube-system

3、访问认证:

认证时的账号必须为ServiceAccount:其作用是被dashboard pod拿来由kubenetes进行认证;认证方式有2种:
token:

  • (1)创建ServiceAccount,根据其管理目标,使用rolebinding或clusterbinding绑定至合理的role或clusterrole;
  • (2)获取此ServiceAccount的secret,查看secret的详细信息,其中就有token;
  • (3)复制token到认证页面即可登录。

kubeconfig:把ServiceAccount的token封装为kubeconfig文件

  • (1)创建ServiceAccount,根据其管理目标,使用rolebinding或clusterbinding绑定至合理的role或clusterrole;
  • (2)kubectl get secret |awk ‘/^ServiceAccount/{print $1}’
  • KUBE_TOKEN=$(kubectl get secret SERVICEACCOUNT_SECRET_NAME -o jsonpath={.data.token} | base64 -d)
  • (3)生成kubeconfig文件
1
2
3
4
kubectl config set-cluster
kubectl config set-credentials NAME --token=$KUBE_TOKEN
kubectl config set-context
kubectl config use-context

k8s学习笔记-18-认证、授权和准入控制

[TOC]

API Server作为Kubernetes网关,是访问和管理资源对象的唯一入口,其各种集群组件访问资源都需要经过网关才能进行正常访问和管理。每一次的访问请求都需要进行合法性的检验,其中包括身份验证、操作权限验证以及操作规范验证等,需要通过一系列验证通过之后才能访问或者存储数据到etcd当中。如下图:

img

一、ServiceAccount

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

  • User account是为人设计的,而service account则是为Pod中的进程调用Kubernetes API而设计;
  • User account是跨namespace的,而service account则是仅局限它所在的namespace;
  • 每个namespace都会自动创建一个default service account
  • Token controller检测service account的创建,并为它们创建secret
  • 开启ServiceAccount Admission Controller后
    • 每个Pod在创建后都会自动设置spec.serviceAccount为default(除非指定了其他ServiceAccout)
    • 验证Pod引用的service account已经存在,否则拒绝创建
    • 如果Pod没有指定ImagePullSecrets,则把service account的ImagePullSecrets加到Pod中
    • 每个container启动后都会挂载该service account的token和ca.crt到/var/run/secrets/kubernetes.io/serviceaccount/

当创建 pod 的时候,如果没有指定一个 service account,系统会自动在与该pod 相同的 namespace 下为其指派一个default service account。而pod和apiserver之间进行通信的账号,称为serviceAccountName。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 34d
filebeat-ds-s466l 1/1 Running 2 34d
myapp-0 1/1 Running 0 3h
myapp-1 1/1 Running 0 3h
myapp-2 1/1 Running 0 4h
myapp-3 1/1 Running 0 4h
pod-vol-demo 2/2 Running 0 2d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 2d
[root@k8s-master ~]# kubectl get pods/myapp-0 -o yaml |grep "serviceAccountName"
serviceAccountName: default
[root@k8s-master ~]# kubectl describe pods myapp-0
Name: myapp-0
Namespace: default
......
Volumes:
......
default-token-j5pf5:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-j5pf5
Optional: false

从上面可以看到每个Pod无论定义与否都会有个存储卷,这个存储卷为default-token-*** token令牌,这就是pod和serviceaccount认证信息。通过secret进行定义,由于认证信息属于敏感信息,所以需要保存在secret资源当中,并以存储卷的方式挂载到Pod当中。从而让Pod内运行的应用通过对应的secret中的信息来连接apiserver,并完成认证。每个 namespace 中都有一个默认的叫做 default 的 service account 资源。进行查看名称空间内的secret,也可以看到对应的default-token。让当前名称空间中所有的pod在连接apiserver时可以使用的预制认证信息,从而保证pod之间的通信。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master ~]# kubectl get sa
NAME SECRETS AGE
default 1 50d
[root@k8s-master ~]# kubectl get sa -n ingress-nginx #前期创建的ingress-nginx名称空间也存在这样的serviceaccount
NAME SECRETS AGE
default 1 11d
nginx-ingress-serviceaccount 1 11d
[root@k8s-master ~]# kubectl get secret
NAME TYPE DATA AGE
default-token-j5pf5 kubernetes.io/service-account-token 3 50d
mysecret Opaque 2 1d
tomcat-ingress-secret kubernetes.io/tls 2 10d
[root@k8s-master ~]# kubectl get secret -n ingress-nginx
NAME TYPE DATA AGE
default-token-zl49j kubernetes.io/service-account-token 3 11d
nginx-ingress-serviceaccount-token-mcsf4 kubernetes.io/service-account-token 3 11d

而默认的service account 仅仅只能获取当前Pod自身的相关属性,无法观察到其他名称空间Pod的相关属性信息。如果想要扩展Pod,假设有一个Pod需要用于管理其他Pod或者是其他资源对象,是无法通过自身的名称空间的serviceaccount进行获取其他Pod的相关属性信息的,此时就需要进行手动创建一个serviceaccount,并在创建Pod时进行定义。那么serviceaccount该如何进行定义呢???实际上,service accout也属于一个k8s资源,如下查看service account的定义方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
[root@k8s-master ~]# kubectl explain sa
KIND: ServiceAccount
VERSION: v1

DESCRIPTION:
ServiceAccount binds together: * a name, understood by users, and perhaps
by peripheral systems, for an identity * a principal that can be
authenticated and authorized * a set of secrets

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

automountServiceAccountToken <boolean>
AutomountServiceAccountToken indicates whether pods running as this service
account should have an API token automatically mounted. Can be overridden
at the pod level.

imagePullSecrets <[]Object>
ImagePullSecrets is a list of references to secrets in the same namespace
to use for pulling any images in pods that reference this ServiceAccount.
ImagePullSecrets are distinct from Secrets because Secrets can be mounted
in the pod, but ImagePullSecrets are only accessed by the kubelet. More
info:
https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

secrets <[]Object>
Secrets is the list of secrets allowed to be used by pods running using
this ServiceAccount. More info:
https://kubernetes.io/docs/concepts/configuration/secret

service account的创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@k8s-master mainfests]# kubectl create serviceaccount mysa -o yaml --dry-run #不执行查看定义方式
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: null
name: mysa
[root@k8s-master mainfests]# kubectl create serviceaccount mysa -o yaml --dry-run > serviceaccount.yaml #直接导出为yaml定义文件,可以节省敲键盘的时间
[root@k8s-master mainfests]# kubectl apply -f serviceaccount.yaml
serviceaccount/mysa created
[root@k8s-master mainfests]# kubectl get serviceaccount/mysa -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"creationTimestamp":null,"name":"mysa","namespace":"default"}}
creationTimestamp: 2018-10-11T08:12:25Z
name: mysa
namespace: default
resourceVersion: "432865"
selfLink: /api/v1/namespaces/default/serviceaccounts/mysa
uid: 62fc7782-cd2d-11e8-801a-000c2972dc1f
secrets:
- name: mysa-token-h2mgk

看到有一个 token 已经被自动创建,并被 service account 引用。设置非默认的 service account,只需要在 pod 的spec.serviceAccountName 字段中将name设置为您想要用的 service account 名字即可。在 pod 创建之初 service account 就必须已经存在,否则创建将被拒绝。需要注意的是不能更新已创建的 pod 的 service account。

serviceaccount的自定义使用

这里在default名称空间创建了一个sa为admin,可以看到已经自动生成了一个Tokens:admin-token-7k5nr。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
[root@k8s-master mainfests]# kubectl create serviceaccount admin
serviceaccount/admin created
[root@k8s-master mainfests]# kubectl get sa
NAME SECRETS AGE
admin 1 3s
default 1 50d
[root@k8s-master mainfests]# kubectl describe sa/admin
Name: admin
Namespace: default
Labels: <none>
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: admin-token-7k5nr
Tokens: admin-token-7k5nr
Events: <none>
[root@k8s-master mainfests]# kubectl get secret
NAME TYPE DATA AGE
admin-token-7k5nr kubernetes.io/service-account-token 3 31s
default-token-j5pf5 kubernetes.io/service-account-token 3 50d
mysecret Opaque 2 1d
tomcat-ingress-secret kubernetes.io/tls 2 10d
[root@k8s-master mainfests]# vim pod-sa-demo.yaml  #Pod中引用新建的serviceaccount
apiVersion: v1
kind: Pod
metadata:
name: pod-sa-demo
namespace: default
labels:
app: myapp
tier: frontend
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
serviceAccountName: admin
[root@k8s-master mainfests]# kubectl apply -f pod-sa-demo.yaml
pod/pod-sa-demo created
[root@k8s-master mainfests]# kubectl describe pods pod-sa-demo
......
Volumes:
admin-token-7k5nr:
Type: Secret (a volume populated by a Secret)
SecretName: admin-token-7k5nr
Optional: false
......

在K8S集群当中,每一个用户对资源的访问都是需要通过apiserver进行通信认证才能进行访问的,那么在此机制当中,对资源的访问可以是token,也可以是通过配置文件的方式进行保存和使用认证信息,可以通过kubectl config进行查看配置,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master mainfests]# kubectl config view
apiVersion: v1
clusters: #集群列表
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.56.11:6443
name: kubernetes
contexts: #上下文列表
- context: #定义哪个集群被哪个用户访问
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes #当前上下文
kind: Config
preferences: {}
users: #用户列表
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

在上面的配置文件当中,定义了集群、上下文以及用户。其中Config也是K8S的标准资源之一,在该配置文件当中定义了一个集群列表,指定的集群可以有多个;用户列表也可以有多个,指明集群中的用户;而在上下文列表当中,是进行定义可以使用哪个用户对哪个集群进行访问,以及当前使用的上下文是什么。如图:定义了用户kubernetes-admin可以对kubernetes该集群的访问,用户kubernetes-user1对Clluster1集群的访问

img

自建证书和账号进行访问apiserver演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
(1)生成证书
[root@k8s-master pki]# (umask 077;openssl genrsa -out magedu.key 2048)
Generating RSA private key, 2048 bit long modulus
............................................................................................+++
...................................................................................+++
e is 65537 (0x10001)
[root@k8s-master pki]# ll magedu.key
-rw------- 1 root root 1675 Oct 12 23:52 magedu.key

(2)使用ca.crt进行签署
[root@k8s-master pki]# openssl req -new -key magedu.key -out magedu.csr -subj "/CN=magedu" 证书签署请求

[root@k8s-master pki]# openssl x509 -req -in magedu.csr -CA ./ca.crt -CAkey ./ca.key -CAcreateserial -out magedu.crt -days 365 #证书签署
Signature ok
subject=/CN=magedu
Getting CA Private Key
[root@k8s-master pki]# openssl x509 -in magedu.crt -text -noout

(3)添加到用户认证
[root@k8s-master pki]# kubectl config set-credentials magedu --client-certificate=./magedu.crt --client-key=./magedu.key --embed-certs=true
User "magedu" set.

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

[root@k8s-master pki]# kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.56.11:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: magedu
name: magedu@kubernetes
current-context: kubernetes-admin@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: magedu
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

[root@k8s-master pki]# kubectl config use-context magedu@kubernetes
Switched to context "magedu@kubernetes".
[root@k8s-master pki]# kubectl get pods
No resources found.
Error from server (Forbidden): pods is forbidden: User "magedu" cannot list pods in the namespace "default"

从上面的演示,当切换成magedu用户进行访问集群时,由于magedu该账户没有管理集群的权限,所以在获取pods资源信息时,会提示Forrbidden。那么下面就再来了解一下怎么对账户进行授权!!!

二、RBAC—-基于角色的访问控制

Kubernetes的授权是基于插件形式的,其常用的授权插件有以下几种:

  • Node(节点认证)
  • ABAC(基于属性的访问控制)
  • RBAC(基于角色的访问控制)
  • Webhook(基于http回调机制的访问控制)

让一个用户(Users)扮演一个角色(Role),角色拥有权限,从而让用户拥有这样的权限,随后在授权机制当中,只需要将权限授予某个角色,此时用户将获取对应角色的权限,从而实现角色的访问控制。如图:

img

基于角色的访问控制(Role-Based Access Control, 即”RBAC”)使用”rbac.authorization.k8s.io” API Group实现授权决策,允许管理员通过Kubernetes API动态配置策略。

在k8s的授权机制当中,采用RBAC的方式进行授权,其工作逻辑是: 把对对象的操作权限定义到一个角色当中,再将用户绑定到该角色,从而使用户得到对应角色的权限。此种方式仅作用于名称空间当中,这是什么意思呢?当User1绑定到Role角色当中,User1就获取了对该NamespaceA的操作权限,但是对NamespaceB是没有权限进行操作的,如get,list等操作。
另外,k8s为此还有一种集群级别的授权机制,就是定义一个集群角色(ClusterRole),对集群内的所有资源都有可操作的权限,从而将User2,User3通过ClusterRoleBinding到ClusterRole,从而使User2、User3拥有集群的操作权限。Role、RoleBinding、ClusterRole和ClusterRoleBinding的关系如下图:

img

但是这里有2种绑定ClusterRoleBinding、RoleBinding。也可以使用RoleBinding去绑定ClusterRole。
当使用这种方式进行绑定时,用户仅能获取当前名称空间的所有权限。为什么这么绕呢??举例有10个名称空间,每个名称空间都需要一个管理员,而该管理员的权限都是一致的。那么此时需要去定义这样的管理员,使用RoleBinding就需要创建10个Role,这样显得更加繁重。为此当使用RoleBinding去绑定一个ClusterRole时,该User仅仅拥有对当前名称空间的集群操作权限,换句话说,此时只需要创建一个ClusterRole就解决了以上的需求。

这里要注意的是:RoleBinding仅仅对当前名称空间有对应的权限。

在RBAC API中,一个角色包含了一套表示一组权限的规则。 权限以纯粹的累加形式累积(没有”否定”的规则)。 角色可以由命名空间(namespace)内的Role对象定义,而整个Kubernetes集群范围内有效的角色则通过ClusterRole对象实现。

三、Kubernetes RBAC的演示

1、User –> Rolebinding –> Role

(1)角色的创建

一个Role对象只能用于授予对某一单一命名空间中资源的访问权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
[root@k8s-master ~]# kubectl create role -h   #查看角色创建帮助
Create a role with single rule.

Examples:
# Create a Role named "pod-reader" that allows user to perform "get", "watch" and "list" on pods
kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods

# Create a Role named "pod-reader" with ResourceName specified
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod

# Create a Role named "foo" with API Group specified
kubectl create role foo --verb=get,list,watch --resource=rs.extensions

# Create a Role named "foo" with SubResource specified
kubectl create role foo --verb=get,list,watch --resource=pods,pods/status

Options:
--allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in
the template. Only applies to golang and jsonpath output formats.
--dry-run=false: If true, only print the object that would be sent, without sending it.
-o, --output='': Output format. One of:
json|yaml|name|go-template|go-template-file|templatefile|template|jsonpath|jsonpath-file.
--resource=[]: Resource that the rule applies to
--resource-name=[]: Resource in the white list that the rule applies to, repeat this flag for multiple items
--save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
--template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--validate=true: If true, use a schema to validate the input before sending it
--verb=[]: Verb that applies to the resources contained in the rule

Usage:
kubectl create role NAME --verb=verb --resource=resource.group/subresource [--resource-name=resourcename] [--dry-run]
[options]
使用kubectl create进行创建角色,指定角色名称,--verb指定权限,--resource指定资源或者资源组,--dry-run单跑模式并不会创建
Use "kubectl options" for a list of global command-line options (applies to all commands).

[root@k8s-master ~]# kubectl create role pods-reader --verb=get,list,watch --resource=pods --dry-run -o yaml #干跑模式查看role的定义

apiVersion: rbac.authorization.k8s.io/v1
kind: Role #资源类型
metadata:
creationTimestamp: null
name: pods-reader
rules:
- apiGroups: #对那些api组内的资源进行操作
- ""
resources: #对那些资源定义
- pods
verbs: #操作权限定义
- get
- list
- watch

[root@k8s-master ~]# cd mainfests/
[root@k8s-master mainfests]# kubectl create role pods-reader --verb=get,list,watch --resource=pods --dry-run -o yaml > role-demo.yaml

[root@k8s-master mainfests]# vim role-demo.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pods-reader
namespace: default
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch

[root@k8s-master mainfests]# kubectl apply -f role-demo.yaml #角色创建
role.rbac.authorization.k8s.io/pods-reader created
[root@k8s-master mainfests]# kubectl get role
NAME AGE
pods-reader 3s
[root@k8s-master mainfests]# kubectl describe role pods-reader
Name: pods-reader
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"rbac.authorization.k8s.io/v1","kind":"Role","metadata":{"annotations":{},"name":"pods-reader","namespace":"default"},"rules":[{"apiGroup...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [get list watch] #此处已经定义了pods-reader这个角色对pods资源拥有get、list、watch的权限

(2)角色的绑定

RoleBinding可以引用在同一命名空间内定义的Role对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
[root@k8s-master ~]# kubectl create rolebinding -h  #角色绑定创建帮助
Create a RoleBinding for a particular Role or ClusterRole.

Examples:
# Create a RoleBinding for user1, user2, and group1 using the admin ClusterRole
kubectl create rolebinding admin --clusterrole=admin --user=user1 --user=user2 --group=group1

Options:
--allow-missing-template-keys=true: If true, ignore any errors in templates when a field or map key is missing in
the template. Only applies to golang and jsonpath output formats.
--clusterrole='': ClusterRole this RoleBinding should reference
--dry-run=false: If true, only print the object that would be sent, without sending it.
--generator='rolebinding.rbac.authorization.k8s.io/v1alpha1': The name of the API generator to use.
--group=[]: Groups to bind to the role
-o, --output='': Output format. One of:
json|yaml|name|templatefile|template|go-template|go-template-file|jsonpath-file|jsonpath.
--role='': Role this RoleBinding should reference
--save-config=false: If true, the configuration of current object will be saved in its annotation. Otherwise, the
annotation will be unchanged. This flag is useful when you want to perform kubectl apply on this object in the future.
--serviceaccount=[]: Service accounts to bind to the role, in the format <namespace>:<name>
--template='': Template string or path to template file to use when -o=go-template, -o=go-template-file. The
template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview].
--validate=true: If true, use a schema to validate the input before sending it

Usage:
kubectl create rolebinding NAME --clusterrole=NAME|--role=NAME [--user=username] [--group=groupname]
[--serviceaccount=namespace:serviceaccountname] [--dry-run] [options]
使用kubectl create进行创建角色绑定,指定角色绑定的名称,--role|--clusterrole指定绑定哪个角色,--user指定哪个用户
Use "kubectl options" for a list of global command-line options (applies to all commands).

[root@k8s-master mainfests]# kubectl create rolebinding magedu-read-pods --role=pods-reader --user=magedu --dry-run -o yaml > rolebinding-demo.yaml
[root@k8s-master mainfests]# cat rolebinding-demo.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
creationTimestamp: null
name: magedu-read-pods
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: pods-reader
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: magedu
[root@k8s-master mainfests]# kubectl apply -f rolebinding-demo.yaml #创建角色绑定
rolebinding.rbac.authorization.k8s.io/magedu-read-pods created

[root@k8s-master mainfests]# kubectl describe rolebinding magedu-read-pods #查看角色绑定的信息,这里可以看到user:magedu绑定到了pods-reader这个角色上
Name: magedu-read-pods
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"rbac.authorization.k8s.io/v1","kind":"RoleBinding","metadata":{"annotations":{},"creationTimestamp":null,"name":"magedu-read-pods","name...
Role:
Kind: Role
Name: pods-reader
Subjects:
Kind Name Namespace
---- ---- ---------
User magedu

[root@k8s-master ~]# kubectl config use-context magedu@kubernetes #切换magedu这个用户,并使用get获取pods资源信息
Switched to context "magedu@kubernetes".
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 36d
filebeat-ds-s466l 1/1 Running 2 36d
myapp-0 1/1 Running 0 2d
myapp-1 1/1 Running 0 2d
myapp-2 1/1 Running 0 2d
myapp-3 1/1 Running 0 2d
pod-sa-demo 1/1 Running 0 1d
pod-vol-demo 2/2 Running 0 3d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 4d
[root@k8s-master ~]# kubectl get pods -n ingress-nginx #测试获取ingress-nginx这个名称空间的pods信息
No resources found.
Error from server (Forbidden): pods is forbidden: User "magedu" cannot list pods in the namespace "ingress-nginx"

从上面的操作,可以总结出,role的定义和绑定,仅作用于当前名称空间,在获取ingress-nginx名称空间时,一样会出现Forbidden!!!

2、User –> Clusterrolebinding –> Clusterrole

(1)clusterrole定义

ClusterRole对象可以授予与Role对象相同的权限,但由于它们属于集群范围对象, 也可以使用它们授予对以下几种资源的访问权限:

  • 集群范围资源(例如节点,即node)
  • 非资源类型endpoint(例如”/healthz”)
  • 跨所有命名空间的命名空间范围资源(例如pod,需要运行命令kubectl get pods --all-namespaces来查询集群中所有的pod)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master mainfests]# kubectl config use-context kubernetes-admin@kubernetes  #切换会kubernetes-admin用户
Switched to context "kubernetes-admin@kubernetes".
[root@k8s-master mainfests]# kubectl create clusterrole cluster-read --verb=get,list,watch --resource=pods -o yaml > clusterrole-demo.yaml

[root@k8s-master mainfests]# vim clusterrole-demo.yaml #定义clusterrole和权限
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cluster-read
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
[root@k8s-master mainfests]# kubectl apply -f clusterrole-demo.yaml #创建clusterrole
clusterrole.rbac.authorization.k8s.io/cluster-read configured

这里我们需要切换回kubernetes-admin账户,是由于magedu账户不具备创建的权限,这也说明普通用户是无法进行创建K8S资源的,除非进行授权。如下,我们另开一个终端,将配置到一个普通用户ik8s上,使其使用magedu账户进行通信

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@k8s-master ~]# useradd ik8s
[root@k8s-master ~]# cp -rp .kube/ /home/ik8s/
[root@k8s-master ~]# chown -R ik8s.ik8s /home/ik8s/
[root@k8s-master ~]# su - ik8s
[ik8s@k8s-master ~]$ kubectl config use-context magedu@kubernetes
Switched to context "magedu@kubernetes".
[ik8s@k8s-master ~]$ kubectl config view
apiVersion: v1
clusters:
- cluster:
certificate-authority-data: REDACTED
server: https://192.168.56.11:6443
name: kubernetes
contexts:
- context:
cluster: kubernetes
user: kubernetes-admin
name: kubernetes-admin@kubernetes
- context:
cluster: kubernetes
user: magedu
name: magedu@kubernetes
current-context: magedu@kubernetes
kind: Config
preferences: {}
users:
- name: kubernetes-admin
user:
client-certificate-data: REDACTED
client-key-data: REDACTED
- name: magedu
user:
client-certificate-data: REDACTED
client-key-data: REDACTED

(2)clusterrolebinding定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
[root@k8s-master mainfests]# kubectl get rolebinding  #获取角色绑定信息
NAME AGE
magedu-read-pods 1h
[root@k8s-master mainfests]# kubectl delete rolebinding magedu-read-pods #删除前面的绑定
rolebinding.rbac.authorization.k8s.io "magedu-read-pods" deleted

[ik8s@k8s-master ~]$ kubectl get pods #删除后,在ik8s普通用户上进行获取pods资源信息,就立马出现forbidden了
No resources found.
Error from server (Forbidden): pods is forbidden: User "magedu" cannot list pods in the namespace "default"

[root@k8s-master mainfests]# kubectl create clusterrolebinding magedu-read-all-pods --clusterrole=cluster-read --user=magedu --dry-run -o yaml > clusterrolebinding-demo.yaml
[root@k8s-master mainfests]# vim clusterrolebinding-demo.yaml #创建角色绑定,将magedu绑定到clusterrole:magedu-read-all-pods上
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
name: magedu-read-all-pods
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-read
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: magedu

[root@k8s-master mainfests]# kubectl get clusterrole
NAME AGE
admin 52d
cluster-admin 52d
cluster-read 13m
......

[root@k8s-master mainfests]# kubectl apply -f clusterrolebinding-demo.yaml
clusterrolebinding.rbac.authorization.k8s.io/magedu-read-all-pods created
[root@k8s-master mainfests]# kubectl get clusterrolebinding
NAME AGE
......
magedu-read-all-pods 10s

[root@k8s-master mainfests]# kubectl describe clusterrolebinding magedu-read-all-pods
Name: magedu-read-all-pods
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"rbac.authorization.k8s.io/v1beta1","kind":"ClusterRoleBinding","metadata":{"annotations":{},"name":"magedu-read-all-pods","namespace":""...
Role:
Kind: ClusterRole
Name: cluster-read
Subjects:
Kind Name Namespace
---- ---- ---------
User magedu

[ik8s@k8s-master ~]$ kubectl get pods #角色绑定后在ik8s终端上进行获取pods信息,已经不会出现forbidden了
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 36d
filebeat-ds-s466l 1/1 Running 2 36d
myapp-0 1/1 Running 0 2d
myapp-1 1/1 Running 0 2d
myapp-2 1/1 Running 0 2d
myapp-3 1/1 Running 0 2d
pod-sa-demo 1/1 Running 0 1d
pod-vol-demo 2/2 Running 0 4d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 4d
[ik8s@k8s-master ~]$ kubectl get pods -n ingress-nginx #更换名称空间进行查看也是可行的
NAME READY STATUS RESTARTS AGE
default-http-backend-7db7c45b69-nqxw9 1/1 Running 1 4d
nginx-ingress-controller-6bd7c597cb-9fzbw 1/1 Running 0 4d

[ik8s@k8s-master ~]$ kubectl delete pods pod-sa-demo #但是进行删除pod就无法进行,因为在授权时是没有delete权限的
Error from server (Forbidden): pods "pod-sa-demo" is forbidden: User "magedu" cannot delete pods in the namespace "default"

从上面的实验,我们可以知道对用户magedu进行集群角色绑定,用户magedu将会获取对集群内所有资源的对应权限。

3、User –> Rolebinding –> Clusterrole

将maedu通过rolebinding到集群角色magedu-read-pods当中,此时,magedu仅作用于当前名称空间的所有pods资源的权限

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master mainfests]# kubectl delete clusterrolebinding magedu-read-all-pods
clusterrolebinding.rbac.authorization.k8s.io "magedu-read-all-pods" deleted
[root@k8s-master mainfests]# kubectl create rolebinding magedu-read-pods --clusterrole=cluster-read --user=magedu --dry-run -o yaml > rolebinding-clusterrole-demo.yaml
[root@k8s-master mainfests]# vim rolebinding-clusterrole-demo.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: magedu-read-pods
namespace: default
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-read
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: magedu

[root@k8s-master mainfests]# kubectl apply -f rolebinding-clusterrole-demo.yaml
rolebinding.rbac.authorization.k8s.io/magedu-read-pods created

[ik8s@k8s-master ~]$ kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 36d
filebeat-ds-s466l 1/1 Running 2 36d
myapp-0 1/1 Running 0 2d
myapp-1 1/1 Running 0 2d
myapp-2 1/1 Running 0 2d
myapp-3 1/1 Running 0 2d
pod-sa-demo 1/1 Running 0 1d
pod-vol-demo 2/2 Running 0 4d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 4d
[ik8s@k8s-master ~]$ kubectl get pods -n ingress-nginx
No resources found.
Error from server (Forbidden): pods is forbidden: User "magedu" cannot list pods in the namespace "ingress-nginx"

四、RBAC的三种授权访问

RBAC不仅仅可以对user进行访问权限的控制,还可以通过group和serviceaccount进行访问权限控制。当我们想对一组用户进行权限分配时,即可将这一组用户归并到一个组内,从而通过对group进行访问权限的分配,达到访问权限控制的效果。

从前面serviceaccount我们可以了解到,Pod可以通过 spec.serviceAccountName来定义其是以某个serviceaccount的身份进行运行,当我们通过RBAC对serviceaccount进行访问授权时,即可以实现Pod对其他资源的访问权限进行控制。也就是说,当我们对serviceaccount进行rolebinding或clusterrolebinding,会使创建Pod拥有对应角色的权限和apiserver进行通信。如图:

img

k8s学习笔记-17-statefulset控制器

[TOC]

一、statefulset简介

    从前面的学习我们知道使用Deployment创建的pod是无状态的,当挂载了Volume之后,如果该pod挂了,Replication Controller会再启动一个pod来保证可用性,但是由于pod是无状态的,pod挂了就会和之前的Volume的关系断开,新创建的Pod无法找到之前的Pod。但是对于用户而言,他们对底层的Pod挂了是没有感知的,但是当Pod挂了之后就无法再使用之前挂载的存储卷。

    为了解决这一问题,就引入了StatefulSet用于保留Pod的状态信息。

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

  • 1、稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
  • 2、稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
  • 3、有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
  • 4、有序收缩,有序删除(即从N-1到0)
  • 5、有序的滚动更新

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

  • Headless Service(无头服务)用于为Pod资源标识符生成可解析的DNS记录。
  • volumeClaimTemplates (存储卷申请模板)基于静态或动态PV供给方式为Pod资源提供专有的固定存储。
  • StatefulSet,用于管控Pod资源。

二、为什么要有headless??

    在deployment中,每一个pod是没有名称,是随机字符串,是无序的。而statefulset中是要求有序的,每一个pod的名称必须是固定的。当节点挂了,重建之后的标识符是不变的,每一个节点的节点名称是不能改变的。pod名称是作为pod识别的唯一标识符,必须保证其标识符的稳定并且唯一。
    为了实现标识符的稳定,这时候就需要一个headless service 解析直达到pod,还需要给pod配置一个唯一的名称。

三、为什么要 有volumeClainTemplate??

    大部分有状态副本集都会用到持久存储,比如分布式系统来说,由于数据是不一样的,每个节点都需要自己专用的存储节点。而在deployment中pod模板中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,而statefulset定义中的每一个pod都不能使用同一个存储卷,由此基于pod模板创建pod是不适应的,这就需要引入volumeClainTemplate,当在使用statefulset创建pod时,会自动生成一个PVC,从而请求绑定一个PV,从而有自己专用的存储卷。Pod名称、PVC和PV关系图如下:

1349539-20190307152625497-1629030475

四、statefulSet使用演示

在创建StatefulSet之前需要准备的东西,值得注意的是创建顺序非常关键,创建顺序如下:
1、Volume
2、Persistent Volume
3、Persistent Volume Claim
4、Service
5、StatefulSet
Volume可以有很多种类型,比如nfs、glusterfs等,我们这里使用的ceph RBD来创建。

(1)查看statefulset的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master ~]# kubectl explain statefulset
KIND: StatefulSet
VERSION: apps/v1

DESCRIPTION:
StatefulSet represents a set of pods with consistent identities. Identities
are defined as: - Network: A single stable DNS and hostname. - Storage: As
many VolumeClaims as requested. The StatefulSet guarantees that a given
network identity will always map to the same storage identity.

FIELDS:
apiVersion <string>
kind <string>
metadata <Object>
spec <Object>
status <Object>
[root@k8s-master ~]# kubectl explain statefulset.spec
KIND: StatefulSet
VERSION: apps/v1

RESOURCE: spec <Object>

DESCRIPTION:
Spec defines the desired identities of pods in this set.

A StatefulSetSpec is the specification of a StatefulSet.

FIELDS:
podManagementPolicy <string> #Pod管理策略
replicas <integer> #副本数量
revisionHistoryLimit <integer> #历史版本限制
selector <Object> -required- #选择器,必选项
serviceName <string> -required- #服务名称,必选项
template <Object> -required- #模板,必选项
updateStrategy <Object> #更新策略
volumeClaimTemplates <[]Object> #存储卷申请模板,列表对象形式

(2)清单定义StatefulSet

如上所述,一个完整的StatefulSet控制器由一个Headless Service、一个StatefulSet和一个volumeClaimTemplate组成。如下资源清单中的定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
[root@k8s-master mainfests]# vim stateful-demo.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc
labels:
app: myapp-svc
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: myapp-pod
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myapp
spec:
serviceName: myapp-svc
replicas: 3
selector:
matchLabels:
app: myapp-pod
template:
metadata:
labels:
app: myapp-pod
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: myappdata
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: myappdata
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 2Gi

解析上例:由于StatefulSet资源依赖于一个实现存在的Headless类型的Service资源,所以需要先定义一个名为myapp-svc的Headless Service资源,用于为关联到每个Pod资源创建DNS资源记录。接着定义了一个名为myapp的StatefulSet资源,它通过Pod模板创建了3个Pod资源副本,并基于volumeClaimTemplates向前面创建的PV进行了请求大小为2Gi的专用存储卷。

(3)删除前期的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master mainfests]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 23h
pv002 2Gi RWO Retain Available 23h
pv003 2Gi RWO,RWX Retain Bound default/mypvc 23h
pv004 4Gi RWO,RWX Retain Available 23h
pv005 5Gi RWO,RWX Retain Available 23h
[root@k8s-master mainfests]# kubectl delete pods pod-vol-pvc
pod "pod-vol-pvc" deleted
[root@k8s-master mainfests]# kubectl delete pods/pod-cm-3 pods/pod-secret-env pods/pod-vol-hostpath
pod "pod-cm-3" deleted
pod "pod-secret-env" deleted
pod "pod-vol-hostpath" deleted

[root@k8s-master mainfests]# kubectl delete deploy/myapp-backend-pod deploy/tomcat-deploy
deployment.extensions "myapp-backend-pod" deleted
deployment.extensions "tomcat-deploy" deleted

[root@k8s-master mainfests]# kubectl delete pods pod-vol-pvc
pod "pod-vol-pvc" deleted
[root@k8s-master mainfests]# kubectl delete pods/pod-cm-3 pods/pod-secret-env pods/pod-vol-hostpath
pod "pod-cm-3" deleted
pod "pod-secret-env" deleted
pod "pod-vol-hostpath" deleted

[root@k8s-master mainfests]# kubectl delete deploy/myapp-backend-pod deploy/tomcat-deploy
deployment.extensions "myapp-backend-pod" deleted
deployment.extensions "tomcat-deploy" deleted

persistentvolumeclaim "mypvc" deleted
[root@k8s-master mainfests]# kubectl delete pv --all
persistentvolume "pv001" deleted
persistentvolume "pv002" deleted
persistentvolume "pv003" deleted
persistentvolume "pv004" deleted
persistentvolume "pv005" deleted

(4)修改pv的大小为2Gi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[root@k8s-master ~]# cd mainfests/volumes
[root@k8s-master volumes]# vim pv-demo.yaml
[root@k8s-master volumes]# kubectl apply -f pv-demo.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created
[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 5s
pv002 2Gi RWO Retain Available 5s
pv003 2Gi RWO,RWX Retain Available 5s
pv004 2Gi RWO,RWX Retain Available 5s
pv005 2Gi RWO,RWX Retain Available 5s

(5)创建statefulset

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
[root@k8s-master mainfests]# kubectl apply -f stateful-demo.yaml 
service/myapp-svc created
statefulset.apps/myapp created
[root@k8s-master mainfests]# kubectl get svc #查看创建的无头服务myapp-svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 50d
myapp-svc ClusterIP None <none> 80/TCP 38s
[root@k8s-master mainfests]# kubectl get sts #查看statefulset
NAME DESIRED CURRENT AGE
myapp 3 3 55s
[root@k8s-master mainfests]# kubectl get pvc #查看pvc绑定
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
myappdata-myapp-0 Bound pv002 2Gi RWO 1m
myappdata-myapp-1 Bound pv003 2Gi RWO,RWX 1m
myappdata-myapp-2 Bound pv004 2Gi RWO,RWX 1m
[root@k8s-master mainfests]# kubectl get pv #查看pv绑定
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 6m
pv002 2Gi RWO Retain Bound default/myappdata-myapp-0 6m
pv003 2Gi RWO,RWX Retain Bound default/myappdata-myapp-1 6m
pv004 2Gi RWO,RWX Retain Bound default/myappdata-myapp-2 6m
pv005 2Gi RWO,RWX Retain Available 6m

[root@k8s-master mainfests]# kubectl get pods #查看Pod信息
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 2m
myapp-1 1/1 Running 0 2m
myapp-2 1/1 Running 0 2m
pod-vol-demo 2/2 Running 0 1d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 2d

当删除的时候是从myapp-2开始进行删除的,关闭是逆向关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master mainfests]# kubectl delete -f stateful-demo.yaml 
service "myapp-svc" deleted
statefulset.apps "myapp" deleted

[root@k8s-master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 1 33d
filebeat-ds-s466l 1/1 Running 2 33d
myapp-0 1/1 Running 0 3m
myapp-1 1/1 Running 0 3m
myapp-2 1/1 Running 0 3m
pod-vol-demo 2/2 Running 0 1d
redis-5b5d6fbbbd-q8ppz 1/1 Running 1 2d
myapp-0 1/1 Terminating 0 3m
myapp-2 1/1 Terminating 0 3m
myapp-1 1/1 Terminating 0 3m
myapp-1 0/1 Terminating 0 3m
myapp-0 0/1 Terminating 0 3m
myapp-2 0/1 Terminating 0 3m
myapp-1 0/1 Terminating 0 3m
myapp-1 0/1 Terminating 0 3m
myapp-0 0/1 Terminating 0 4m
myapp-0 0/1 Terminating 0 4m
myapp-2 0/1 Terminating 0 3m
myapp-2 0/1 Terminating 0 3m

此时PVC依旧存在的,再重新创建pod时,依旧会重新去绑定原来的pvc
[root@k8s-master mainfests]# kubectl apply -f stateful-demo.yaml
service/myapp-svc created
statefulset.apps/myapp created

[root@k8s-master mainfests]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
myappdata-myapp-0 Bound pv002 2Gi RWO 5m
myappdata-myapp-1 Bound pv003 2Gi RWO,RWX 5m
myappdata-myapp-2 Bound pv004 2Gi RWO,RWX 5m

五、滚动更新、扩展伸缩、版本升级、修改更新策略

1、滚动更新

RollingUpdate 更新策略在 StatefulSet 中实现 Pod 的自动滚动更新。 当StatefulSet的 .spec.updateStrategy.type 设置为 RollingUpdate 时,默认为:RollingUpdate。StatefulSet 控制器将在 StatefulSet 中删除并重新创建每个 Pod。 它将以与 Pod 终止相同的顺序进行(从最大的序数到最小的序数),每次更新一个 Pod。 在更新其前身之前,它将等待正在更新的 Pod 状态变成正在运行并就绪。如下操作的滚动更新是有2-0的顺序更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@k8s-master mainfests]# vim stateful-demo.yaml  #修改image版本为v2
.....
image: ikubernetes/myapp:v2
....
[root@k8s-master mainfests]# kubectl apply -f stateful-demo.yaml
service/myapp-svc unchanged
statefulset.apps/myapp configured
[root@k8s-master ~]# kubectl get pods -w #查看滚动更新的过程
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 36m
myapp-1 1/1 Running 0 36m
myapp-2 1/1 Running 0 36m

myapp-2 1/1 Terminating 0 36m
myapp-2 0/1 Terminating 0 36m
myapp-2 0/1 Terminating 0 36m
myapp-2 0/1 Terminating 0 36m
myapp-2 0/1 Pending 0 0s
myapp-2 0/1 Pending 0 0s
myapp-2 0/1 ContainerCreating 0 0s
myapp-2 1/1 Running 0 2s
myapp-1 1/1 Terminating 0 36m
myapp-1 0/1 Terminating 0 36m
myapp-1 0/1 Terminating 0 36m
myapp-1 0/1 Terminating 0 36m
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 ContainerCreating 0 0s
myapp-1 1/1 Running 0 1s
myapp-0 1/1 Terminating 0 37m
myapp-0 0/1 Terminating 0 37m
myapp-0 0/1 Terminating 0 37m
myapp-0 0/1 Terminating 0 37m

在创建的每一个Pod中,每一个pod自己的名称都是可以被解析的,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[root@k8s-master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
myapp-0 1/1 Running 0 8m 10.244.1.62 k8s-node01
myapp-1 1/1 Running 0 8m 10.244.2.49 k8s-node02
myapp-2 1/1 Running 0 8m 10.244.1.61 k8s-node01

[root@k8s-master mainfests]# kubectl exec -it myapp-0 -- /bin/sh
/ # nslookup myapp-0.myapp-svc.default.svc.cluster.local
nslookup: can't resolve '(null)': Name does not resolve

Name: myapp-0.myapp-svc.default.svc.cluster.local
Address 1: 10.244.1.62 myapp-0.myapp-svc.default.svc.cluster.local
/ # nslookup myapp-1.myapp-svc.default.svc.cluster.local
nslookup: can't resolve '(null)': Name does not resolve

Name: myapp-1.myapp-svc.default.svc.cluster.local
Address 1: 10.244.2.49 myapp-1.myapp-svc.default.svc.cluster.local
/ # nslookup myapp-2.myapp-svc.default.svc.cluster.local
nslookup: can't resolve '(null)': Name does not resolve

Name: myapp-2.myapp-svc.default.svc.cluster.local
Address 1: 10.244.1.61 myapp-2.myapp-svc.default.svc.cluster.local

从上面的解析,我们可以看到在容器当中可以通过对Pod的名称进行解析到ip。其解析的域名格式如下:
pod_name.service_name.ns_name.svc.cluster.local
eg: myapp-0.myapp.default.svc.cluster.local

2、扩展伸缩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master mainfests]# kubectl scale sts myapp --replicas=4  #扩容副本增加到4个
statefulset.apps/myapp scaled
[root@k8s-master ~]# kubectl get pods -w #动态查看扩容
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 23m
myapp-1 1/1 Running 0 23m
myapp-2 1/1 Running 0 23m

myapp-3 0/1 Pending 0 0s
myapp-3 0/1 Pending 0 0s
myapp-3 0/1 ContainerCreating 0 0s
myapp-3 1/1 Running 0 1s
[root@k8s-master mainfests]# kubectl get pv #查看pv绑定
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 1h
pv002 2Gi RWO Retain Bound default/myappdata-myapp-0 1h
pv003 2Gi RWO,RWX Retain Bound default/myappdata-myapp-1 1h
pv004 2Gi RWO,RWX Retain Bound default/myappdata-myapp-2 1h
pv005 2Gi RWO,RWX Retain Bound default/myappdata-myapp-3 1h

[root@k8s-master mainfests]# kubectl patch sts myapp -p '{"spec":{"replicas":2}}' #打补丁方式缩容
statefulset.apps/myapp patched
[root@k8s-master ~]# kubectl get pods -w #动态查看缩容
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 25m
myapp-1 1/1 Running 0 25m
myapp-2 1/1 Running 0 25m
myapp-3 1/1 Running 0 1m
myapp-3 1/1 Terminating 0 2m
myapp-3 0/1 Terminating 0 2m
myapp-3 0/1 Terminating 0 2m
myapp-3 0/1 Terminating 0 2m
myapp-2 1/1 Terminating 0 26m
myapp-2 0/1 Terminating 0 26m
myapp-2 0/1 Terminating 0 27m
myapp-2 0/1 Terminating 0 27m

3、更新策略和版本升级

修改更新策略,以partition方式进行更新,更新值为2,只有myapp编号大于等于2的才会进行更新。类似于金丝雀部署方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master mainfests]# kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":2}}}}'
statefulset.apps/myapp patched
[root@k8s-master ~]# kubectl get sts myapp
NAME DESIRED CURRENT AGE
myapp 4 4 1h
[root@k8s-master ~]# kubectl describe sts myapp
Name: myapp
Namespace: default
CreationTimestamp: Wed, 10 Oct 2018 21:58:24 -0400
Selector: app=myapp-pod
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"apps/v1","kind":"StatefulSet","metadata":{"annotations":{},"name":"myapp","namespace":"default"},"spec":{"replicas":3,"selector":{"match...
Replicas: 4 desired | 4 total
Update Strategy: RollingUpdate
Partition: 2
......

版本升级,将image的版本升级为v3,升级后对比myapp-2和myapp-1的image版本是不同的。这样就实现了金丝雀发布的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@k8s-master mainfests]# kubectl set image sts/myapp myapp=ikubernetes/myapp:v3
statefulset.apps/myapp image updated
[root@k8s-master ~]# kubectl get sts -o wide
NAME DESIRED CURRENT AGE CONTAINERS IMAGES
myapp 4 4 1h myapp ikubernetes/myapp:v3
[root@k8s-master ~]# kubectl get pods myapp-2 -o yaml |grep image
- image: ikubernetes/myapp:v3
imagePullPolicy: IfNotPresent
image: ikubernetes/myapp:v3
imageID: docker-pullable://ikubernetes/myapp@sha256:b8d74db2515d3c1391c78c5768272b9344428035ef6d72158fd9f6c4239b2c69

[root@k8s-master ~]# kubectl get pods myapp-1 -o yaml |grep image
- image: ikubernetes/myapp:v2
imagePullPolicy: IfNotPresent
image: ikubernetes/myapp:v2
imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358

将剩余的Pod也更新版本,只需要将更新策略的partition值改为0即可,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@k8s-master mainfests]#  kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":0}}}}'
statefulset.apps/myapp patched

[root@k8s-master ~]# kubectl get pods -w
NAME READY STATUS RESTARTS AGE
myapp-0 1/1 Running 0 58m
myapp-1 1/1 Running 0 58m
myapp-2 1/1 Running 0 13m
myapp-3 1/1 Running 0 13m
myapp-1 1/1 Terminating 0 58m
myapp-1 0/1 Terminating 0 58m
myapp-1 0/1 Terminating 0 58m
myapp-1 0/1 Terminating 0 58m
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 Pending 0 0s
myapp-1 0/1 ContainerCreating 0 0s
myapp-1 1/1 Running 0 2s
myapp-0 1/1 Terminating 0 58m
myapp-0 0/1 Terminating 0 58m
myapp-0 0/1 Terminating 0 58m
myapp-0 0/1 Terminating 0 58m
myapp-0 0/1 Pending 0 0s
myapp-0 0/1 Pending 0 0s
myapp-0 0/1 ContainerCreating 0 0s
myapp-0 1/1 Running 0 2s

k8s学习笔记-16-存储卷

[TOC]

一、存储卷的概念和类型

为了保证数据的持久性,必须保证数据在外部存储在docker容器中,为了实现数据的持久性存储,在宿主机和容器内做映射,可以保证在容器的生命周期结束,数据依旧可以实现持久性存储。但是在k8s中,由于pod分布在各个不同的节点之上,并不能实现不同节点之间持久性数据的共享,并且,在节点故障时,可能会导致数据的永久性丢失。为此,k8s就引入了外部存储卷的功能。
k8s的存储卷类型:

1
2
3
4
5
6
7
8
9
10
[root@k8s-master ~]# kubectl explain pod.spec.volumes #查看k8s支持的存储类型
KIND: Pod
VERSION: v1

常用分类:
emptyDir(临时目录):Pod删除,数据也会被清除,这种存储成为emptyDir,用于数据的临时存储。
hostPath(宿主机目录映射):
本地的SAN(iSCSI,FC)、NAS(nfs,cifs,http)存储
分布式存储(glusterfs,rbd,cephfs)
云存储(EBS,Azure Disk)

persistentVolumeClaim –>PVC(存储卷创建申请)
当你需要创建一个存储卷时,只需要进行申请对应的存储空间即可使用,这就是PVC。其关联关系如图:

img

上图解析:在Pod上定义一个PVC,该PVC要关联到当前名称空间的PVC资源,该PVC只是一个申请,PVC需要和PV进行关联。PV属于存储上的一部分存储空间。但是该方案存在的问题是,我们无法知道用户是什么时候去创建Pod,也不知道创建Pod时定义多大的PVC,那么如何实现按需创建呢???

不需要PV层,把所有存储空间抽象出来,这一个抽象层称为存储类,当用户创建PVC需要用到PV时,可以向存储类申请对应的存储空间,存储类会按照需求创建对应的存储空间,这就是PV的动态供给,如图:

img

那么PV的动态供给,其重点是在存储类的定义,其分类大概是对存储的性能进行分类的,如图:金存储类、银存储类、铜存储类等。

img

总结:
k8s要使用存储卷,需要2步:
1、在pod定义volume,并指明关联到哪个存储设备
2、在容器使用volume mount进行挂载

二、emptyDir存储卷演示

一个emptyDir 第一次创建是在一个pod被指定到具体node的时候,并且会一直存在在pod的生命周期当中,正如它的名字一样,它初始化是一个空的目录,pod中的容器都可以读写这个目录,这个目录可以被挂在到各个容器相同或者不相同的的路径下。当一个pod因为任何原因被移除的时候,这些数据会被永久删除。注意:一个容器崩溃了不会导致数据的丢失,因为容器的崩溃并不移除pod.

emptyDir 磁盘的作用:
(1)普通空间,基于磁盘的数据存储
(2)作为从崩溃中恢复的备份点
(3)存储那些那些需要长久保存的数据,例web服务中的数据
默认的,emptyDir 磁盘会存储在主机所使用的媒介上,可能是SSD,或者网络硬盘,这主要取决于你的环境。当然,我们也可以将emptyDir.medium的值设置为Memory来告诉Kubernetes 来挂在一个基于内存的目录tmpfs,因为
tmpfs速度会比硬盘块度了,但是,当主机重启的时候所有的数据都会丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
[root@k8s-master ~]# kubectl explain pods.spec.volumes.emptyDir  #查看emptyDir存储定义
[root@k8s-master ~]# kubectl explain pods.spec.containers.volumeMounts #查看容器挂载方式
[root@k8s-master ~]# cd mainfests && mkdir volumes && cd volumes
[root@k8s-master volumes]# cp ../pod-demo.yaml ./
[root@k8s-master volumes]# mv pod-demo.yaml pod-vol-demo.yaml
[root@k8s-master volumes]# vim pod-vol-demo.yaml #创建emptyDir的清单
apiVersion: v1
kind: Pod
metadata:
name: pod-demo
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/create-by:"cluster admin"
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
volumeMounts: #在容器内定义挂载存储名称和挂载路径
- name: html
mountPath: /usr/share/nginx/html/
- name: busybox
image: busybox:latest
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /data/ #在容器内定义挂载存储名称和挂载路径
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
volumes: #定义存储卷
- name: html #定义存储卷名称
emptyDir: {} #定义存储卷类型
[root@k8s-master volumes]# kubectl apply -f pod-vol-demo.yaml
pod/pod-vol-demo created
[root@k8s-master volumes]# kubectl get pods
NAME READY STATUS RESTARTS AGE
pod-vol-demo 2/2 Running 0 27s
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
......
pod-vol-demo 2/2 Running 0 16s 10.244.2.34 k8s-node02
......

在上面,我们定义了2个容器,其中一个容器是输入日期到index.html中,然后验证访问nginx的html是否可以获取日期。以验证两个容器之间挂载的emptyDir实现共享。如下访问验证:
[root@k8s-master volumes]# curl 10.244.2.34 #访问验证
Tue Oct 9 03:56:53 UTC 2018
Tue Oct 9 03:56:55 UTC 2018
Tue Oct 9 03:56:57 UTC 2018
Tue Oct 9 03:56:59 UTC 2018
Tue Oct 9 03:57:01 UTC 2018
Tue Oct 9 03:57:03 UTC 2018
Tue Oct 9 03:57:05 UTC 2018
Tue Oct 9 03:57:07 UTC 2018
Tue Oct 9 03:57:09 UTC 2018
Tue Oct 9 03:57:11 UTC 2018
Tue Oct 9 03:57:13 UTC 2018
Tue Oct 9 03:57:15 UTC 2018

三、hostPath存储卷演示

hostPath宿主机路径,就是把pod所在的宿主机之上的脱离pod中的容器名称空间的之外的宿主机的文件系统的某一目录和pod建立关联关系,在pod删除时,存储数据不会丢失。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
(1)查看hostPath存储类型定义
[root@k8s-master volumes]# kubectl explain pods.spec.volumes.hostPath
KIND: Pod
VERSION: v1

RESOURCE: hostPath <Object>

DESCRIPTION:
HostPath represents a pre-existing file or directory on the host machine
that is directly exposed to the container. This is generally used for
system agents or other privileged things that are allowed to see the host
machine. Most containers will NOT need this. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

Represents a host path mapped into a pod. Host path volumes do not support
ownership management or SELinux relabeling.

FIELDS:
path <string> -required- #指定宿主机的路径
Path of the directory on the host. If the path is a symlink, it will follow
the link to the real path. More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

type <string>
Type for HostPath Volume Defaults to "" More info:
https://kubernetes.io/docs/concepts/storage/volumes#hostpath

type:
DirectoryOrCreate 宿主机上不存在创建此目录
Directory 必须存在挂载目录
FileOrCreate 宿主机上不存在挂载文件就创建
File 必须存在文件

(2)清单定义
[root@k8s-master volumes]# vim pod-hostpath-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-hostpath
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
hostPath:
path: /data/pod/volume1
type: DirectoryOrCreate

(3)在node节点上创建挂载目录
[root@k8s-node01 ~]# mkdir -p /data/pod/volume1
[root@k8s-node01 ~]# vim /data/pod/volume1/index.html
node01.magedu.com
[root@k8s-node02 ~]# mkdir -p /data/pod/volume1
[root@k8s-node02 ~]# vim /data/pod/volume1/index.html
node02.magedu.com
[root@k8s-master volumes]# kubectl apply -f pod-hostpath-vol.yaml
pod/pod-vol-hostpath created

(4)访问测试
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
......
pod-vol-hostpath 1/1 Running 0 37s 10.244.2.35 k8s-node02
......
[root@k8s-master volumes]# curl 10.244.2.35
node02.magedu.com
[root@k8s-master volumes]# kubectl delete -f pod-hostpath-vol.yaml #删除pod,再重建,验证是否依旧可以访问原来的内容
[root@k8s-master volumes]# kubectl apply -f pod-hostpath-vol.yaml
pod/pod-vol-hostpath created
[root@k8s-master volumes]# curl 10.244.2.37
node02.magedu.com

hostPath可以实现持久存储,但是在node节点故障时,也会导致数据的丢失

四、nfs共享存储卷演示

nfs使的我们可以挂在已经存在的共享到的我们的Pod中,和emptyDir不同的是,emptyDir会被删除当我们的Pod被删除的时候,但是nfs不会被删除,仅仅是解除挂在状态而已,这就意味着NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递.并且,nfs可以同时被多个pod挂在并进行读写

注意:在我们进行挂载nfs的时候,必须先保证NFS服务器正常运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
(1)在stor01节点上安装nfs,并配置nfs服务
[root@stor01 ~]# yum install -y nfs-utils ==》192.168.56.14
[root@stor01 ~]# mkdir /data/volumes -pv
[root@stor01 ~]# vim /etc/exports
/data/volumes 192.168.56.0/24(rw,no_root_squash)
[root@stor01 ~]# systemctl start nfs
[root@stor01 ~]# showmount -e
Export list for stor01:
/data/volumes 192.168.56.0/24

(2)在node01和node02节点上安装nfs-utils,并测试挂载
[root@k8s-node01 ~]# yum install -y nfs-utils
[root@k8s-node02 ~]# yum install -y nfs-utils
[root@k8s-node02 ~]# mount -t nfs stor01:/data/volumes /mnt
[root@k8s-node02 ~]# mount
......
stor01:/data/volumes on /mnt type nfs4 (rw,relatime,vers=4.1,rsize=131072,wsize=131072,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.56.13,local_lock=none,addr=192.168.56.14)
[root@k8s-node02 ~]# umount /mnt/

(3)创建nfs存储卷的使用清单
[root@k8s-master volumes]# cp pod-hostpath-vol.yaml pod-nfs-vol.yaml
[root@k8s-master volumes]# vim pod-nfs-vol.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-nfs
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
nfs:
path: /data/volumes
server: stor01
[root@k8s-master volumes]# kubectl apply -f pod-nfs-vol.yaml
pod/pod-vol-nfs created
[root@k8s-master volumes]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-vol-nfs 1/1 Running 0 21s 10.244.2.38 k8s-node02

(3)在nfs服务器上创建index.html
[root@stor01 ~]# cd /data/volumes
[root@stor01 volumes ~]# vim index.html
<h1> nfs stor01</h1>
[root@k8s-master volumes]# curl 10.244.2.38
<h1> nfs stor01</h1>
[root@k8s-master volumes]# kubectl delete -f pod-nfs-vol.yaml #删除nfs相关pod,再重新创建,可以得到数据的持久化存储
pod "pod-vol-nfs" deleted
[root@k8s-master volumes]# kubectl apply -f pod-nfs-vol.yaml

五、PVC和PV的概念

我们前面提到kubernetes提供那么多存储接口,但是首先kubernetes的各个Node节点能管理这些存储,但是各种存储参数也需要专业的存储工程师才能了解,由此我们的kubernetes管理变的更加复杂的。由此kubernetes提出了PV和PVC的概念,这样开发人员和使用者就不需要关注后端存储是什么,使用什么参数等问题。如下图:

img

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

PersistentVolumeClaim(PVC)是用户存储的请求。PVC的使用逻辑:在pod中定义一个存储卷(该存储卷类型为PVC),定义的时候直接指定大小,pvc必须与对应的pv建立关系,pvc会根据定义去pv申请,而pv是由存储空间创建出来的。pv和pvc是kubernetes抽象出来的一种存储资源。

虽然PersistentVolumeClaims允许用户使用抽象存储资源,但是常见的需求是,用户需要根据不同的需求去创建PV,用于不同的场景。而此时需要集群管理员提供不同需求的PV,而不仅仅是PV的大小和访问模式,但又不需要用户了解这些卷的实现细节。 对于这样的需求,此时可以采用StorageClass资源。这个在前面就已经提到过此方案。

PV是集群中的资源。 PVC是对这些资源的请求,也是对资源的索赔检查。 PV和PVC之间的相互作用遵循这个生命周期:

1
Provisioning(配置)---> Binding(绑定)--->Using(使用)---> Releasing(释放) ---> Recycling(回收)

Provisioning

这里有两种PV的提供方式:静态或者动态

静态–>直接固定存储空间:
    集群管理员创建一些 PV。它们携带可供集群用户使用的真实存储的详细信息。 它们存在于Kubernetes API中,可用于消费。

动态–>通过存储类进行动态创建存储空间:
    当管理员创建的静态 PV 都不匹配用户的 PVC 时,集群可能会尝试动态地为 PVC 配置卷。此配置基于 StorageClasses:PVC 必须请求存储类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。

Binding

在动态配置的情况下,用户创建或已经创建了具有特定数量的存储请求和特定访问模式的PersistentVolumeClaim。 主机中的控制回路监视新的PVC,找到匹配的PV(如果可能),并将 PVC 和 PV 绑定在一起。 如果为新的PVC动态配置PV,则循环将始终将该PV绑定到PVC。 否则,用户总是至少得到他们要求的内容,但是卷可能超出了要求。 一旦绑定,PersistentVolumeClaim绑定是排他的,不管用于绑定它们的模式。

如果匹配的卷不存在,PVC将保持无限期。 随着匹配卷变得可用,PVC将被绑定。 例如,提供许多50Gi PV的集群将不匹配要求100Gi的PVC。 当集群中添加100Gi PV时,可以绑定PVC。

Using

Pod使用PVC作为卷。 集群检查声明以找到绑定的卷并挂载该卷的卷。 对于支持多种访问模式的卷,用户在将其声明用作pod中的卷时指定所需的模式。

一旦用户有声明并且该声明被绑定,绑定的PV属于用户,只要他们需要它。 用户通过在其Pod的卷块中包含PersistentVolumeClaim来安排Pods并访问其声明的PV。

Releasing

当用户完成卷时,他们可以从允许资源回收的API中删除PVC对象。 当声明被删除时,卷被认为是“释放的”,但是它还不能用于另一个声明。 以前的索赔人的数据仍然保留在必须根据政策处理的卷上.

Reclaiming

PersistentVolume的回收策略告诉集群在释放其声明后,该卷应该如何处理。 目前,卷可以是保留,回收或删除。 保留可以手动回收资源。 对于那些支持它的卷插件,删除将从Kubernetes中删除PersistentVolume对象,以及删除外部基础架构(如AWS EBS,GCE PD,Azure Disk或Cinder卷)中关联的存储资产。 动态配置的卷始终被删除

Recycling

如果受适当的卷插件支持,回收将对卷执行基本的擦除(rm -rf / thevolume / *),并使其再次可用于新的声明。

六、NFS使用PV和PVC

实验图如下:

img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@k8s-master ~]# kubectl explain pv    #查看pv的定义方式
FIELDS:
apiVersion
kind
metadata
spec
[root@k8s-master ~]# kubectl explain pv.spec #查看pv定义的规格
spec:
nfs(定义存储类型)
path(定义挂载卷路径)
server(定义服务器名称)
accessModes(定义访问模型,有以下三种访问模型,以列表的方式存在,也就是说可以定义多个访问模式)
ReadWriteOnce(RWO) 单节点读写
ReadOnlyMany(ROX) 多节点只读
ReadWriteMany(RWX) 多节点读写
capacity(定义PV空间的大小)
storage(指定大小)

[root@k8s-master volumes]# kubectl explain pvc #查看PVC的定义方式
KIND: PersistentVolumeClaim
VERSION: v1
FIELDS:
apiVersion <string>
kind <string>
metadata <Object>
spec <Object>
[root@k8s-master volumes]# kubectl explain pvc.spec
spec:
accessModes(定义访问模式,必须是PV的访问模式的子集)
resources(定义申请资源的大小)
requests:
storage:

1、配置nfs存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@stor01 volumes]# mkdir v{1,2,3,4,5}
[root@stor01 volumes]# vim /etc/exports
/data/volumes/v1 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v2 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v3 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v4 192.168.56.0/24(rw,no_root_squash)
/data/volumes/v5 192.168.56.0/24(rw,no_root_squash)
[root@stor01 volumes]# exportfs -arv
exporting 192.168.56.0/24:/data/volumes/v5
exporting 192.168.56.0/24:/data/volumes/v4
exporting 192.168.56.0/24:/data/volumes/v3
exporting 192.168.56.0/24:/data/volumes/v2
exporting 192.168.56.0/24:/data/volumes/v1
[root@stor01 volumes]# showmount -e
Export list for stor01:
/data/volumes/v5 192.168.56.0/24
/data/volumes/v4 192.168.56.0/24
/data/volumes/v3 192.168.56.0/24
/data/volumes/v2 192.168.56.0/24
/data/volumes/v1 192.168.56.0/24

2、定义PV

这里定义5个PV,并且定义挂载的路径以及访问模式,还有PV划分的大小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
[root@k8s-master volumes]# kubectl explain pv
[root@k8s-master volumes]# kubectl explain pv.spec.nfs
[root@k8s-master volumes]# vim pv-demo.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 1Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: stor01
accessModes: ["ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: stor01
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 5Gi
[root@k8s-master volumes]# kubectl apply -f pv-demo.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created
[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 7s
pv002 2Gi RWO Retain Available 7s
pv003 2Gi RWO,RWX Retain Available 7s
pv004 4Gi RWO,RWX Retain Available 7s
pv005 5Gi RWO,RWX Retain Available 7s

3、定义PVC

这里定义了pvc的访问模式为多路读写,该访问模式必须在前面pv定义的访问模式之中。定义PVC申请的大小为2Gi,此时PVC会自动去匹配多路读写且大小为2Gi的PV,匹配成功获取PVC的状态即为Bound

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@k8s-master volumes ~]# vim pod-vol-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Pod
metadata:
name: pod-vol-pvc
namespace: default
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
[root@k8s-master volumes]# kubectl apply -f pod-vol-pvc.yaml
persistentvolumeclaim/mypvc created
pod/pod-vol-pvc created
[root@k8s-master volumes]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO,RWX Retain Available 19m
pv002 2Gi RWO Retain Available 19m
pv003 2Gi RWO,RWX Retain Bound default/mypvc 19m
pv004 4Gi RWO,RWX Retain Available 19m
pv005 5Gi RWO,RWX Retain Available 19m
[root@k8s-master volumes]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv003 2Gi RWO,RWX 22s

4、测试访问

在存储服务器上创建index.html,并写入数据,通过访问Pod进行查看,可以获取到相应的页面。

1
2
3
4
5
6
[root@stor01 volumes]# cd v3/
[root@stor01 v3]# echo "welcome to use pv3" > index.html
[root@k8s-master volumes]# kubectl get pods -o wide
pod-vol-pvc 1/1 Running 0 3m 10.244.2.39 k8s-node02
[root@k8s-master volumes]# curl 10.244.2.39
welcome to use pv3

七、StorageClass

在pv和pvc使用过程中存在的问题,在pvc申请存储空间时,未必就有现成的pv符合pvc申请的需求,上面nfs在做pvc可以成功的因素是因为我们做了指定的需求处理。那么当PVC申请的存储空间不一定有满足PVC要求的PV事,又该如何处理呢???为此,Kubernetes为管理员提供了描述存储”class(类)”的方法(StorageClass)。举个例子,在存储系统中划分一个1TB的存储空间提供给Kubernetes使用,当用户需要一个10G的PVC时,会立即通过restful发送请求,从而让存储空间创建一个10G的image,之后在我们的集群中定义成10G的PV供给给当前的PVC作为挂载使用。在此之前我们的存储系统必须支持restful接口,比如ceph分布式存储,而glusterfs则需要借助第三方接口完成这样的请求。如图:
img

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[root@k8s-master ~]# kubectl explain storageclass  #storageclass也是k8s上的资源
KIND: StorageClass
VERSION: storage.k8s.io/v1
FIELDS:
allowVolumeExpansion <boolean>
allowedTopologies <[]Object>
apiVersion <string>
kind <string>
metadata <Object>
mountOptions <[]string> #挂载选项
parameters <map[string]string> #参数,取决于分配器,可以接受不同的参数。 例如,参数 type 的值 io1 和参数 iopsPerGB 特定于 EBS PV。当参数被省略时,会使用默认值。
provisioner <string> -required- #存储分配器,用来决定使用哪个卷插件分配 PV。该字段必须指定。
reclaimPolicy <string> #回收策略,可以是 Delete 或者 Retain。如果 StorageClass 对象被创建时没有指定 reclaimPolicy ,它将默认为 Delete。
volumeBindingMode <string> #卷的绑定模式

StorageClass 中包含 provisioner、parameters 和 reclaimPolicy 字段,当 class 需要动态分配 PersistentVolume 时会使用到。由于StorageClass需要一个独立的存储系统,此处就不再演示。从其他资料查看定义StorageClass的方式如下:

1
2
3
4
5
6
7
8
9
10
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
type: gp2
reclaimPolicy: Retain
mountOptions:
- debug

八、配置容器应用:Secret和configMap

在日常单机甚至集群状态下,我们需要对一个应用进行配置,只需要修改其配置文件即可。那么在容器中又该如何提供配置 信息呢???例如,为Nginx配置一个指定的server_name或worker进程数,为Tomcat的JVM配置其堆内存大小。传统的实践过程中通常有以下几种方式:

  • 启动容器时,通过命令传递参数
  • 将定义好的配置文件通过镜像文件进行写入
  • 通过环境变量的方式传递配置数据
  • 挂载Docker卷传送配置文件
    而在Kubernetes系统之中也存在这样的组件,就是特殊的存储卷类型。其并不是提供pod存储空间,而是给管理员或用户提供从集群外部向Pod内部的应用注入配置信息的方式。这两种特殊类型的存储卷分别是:configMap和secret
  • Secret:用于向Pod传递敏感信息,比如密码,私钥,证书文件等,这些信息如果在容器中定义容易泄露,Secret资源可以让用户将这些信息存储在急群众,然后通过Pod进行挂载,实现敏感数据和系统解耦的效果。
  • ConfigMap:主要用于向Pod注入非敏感数据,使用时,用户将数据直接存储在ConfigMap对象当中,然后Pod通过使用ConfigMap卷进行引用,实现容器的配置文件集中定义和管理。
1
2
3
4
5
6
7
8
[root@k8s-master volumes]# kubectl explain pods.spec.volumes
......
configMap <Object>
ConfigMap represents a configMap that should populate this volume

secret <Object>
Secret represents a secret that should populate this volume. More info:
https://kubernetes.io/docs/concepts/storage/volumes#secret

8.1、Secret解析

Secret对象存储数据的方式是以键值方式存储数据,在Pod资源进行调用Secret的方式是通过环境变量或者存储卷的方式进行访问数据,解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。另外,Secret对象的数据存储和打印格式为Base64编码的字符串,因此用户在创建Secret对象时,也需要提供该类型的编码格式的数据。在容器中以环境变量或存储卷的方式访问时,会自动解码为明文格式。需要注意的是,如果是在Master节点上,Secret对象以非加密的格式存储在etcd中,所以需要对etcd的管理和权限进行严格控制。

Secret有4种类型:

  • Service Account :用来访问Kubernetes API,由Kubernetes自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中;
  • Opaque :base64编码格式的Secret,用来存储密码、密钥、信息、证书等,类型标识符为generic;
  • kubernetes.io/dockerconfigjson :用来存储私有docker registry的认证信息,类型标识为docker-registry。
  • kubernetes.io/tls:用于为SSL通信模式存储证书和私钥文件,命令式创建类型标识为tls。

创建 Secret的2种方式

命令式创建

  • 1、通过 –from-literal:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master ~]# kubectl create secret -h
Create a secret using specified subcommand.

Available Commands:
docker-registry Create a secret for use with a Docker registry
generic Create a secret from a local file, directory or literal value
tls Create a TLS secret

Usage:
kubectl create secret [flags] [options]

Use "kubectl <command> --help" for more information about a given command.
Use "kubectl options" for a list of global command-line options (applies to all commands).

每个 --from-literal 对应一个信息条目。
[root@k8s-master ~]# kubectl create secret generic mysecret --from-literal=username=admin --from-literal=password=123456
secret/mysecret created
[root@k8s-master ~]# kubectl get secret
NAME TYPE DATA AGE
mysecret Opaque 2 6s
  • 2、通过 –from-file:
    每个文件内容对应一个信息条目。
1
2
3
4
5
6
7
[root@k8s-master ~]# echo -n admin > ./username
[root@k8s-master ~]# echo -n 123456 > ./password
[root@k8s-master ~]# kubectl create secret generic mysecret --from-file=./username --from-file=./password
secret/mysecret created
[root@k8s-master ~]# kubectl get secret
NAME TYPE DATA AGE
mysecret Opaque 2 6s
  • 3、通过 –from-env-file:
    文件 env.txt 中每行 Key=Value 对应一个信息条目。
1
2
3
4
5
6
7
8
9
[root@k8s-master ~]# cat << EOF > env.txt
> username=admin
> password=123456
> EOF
[root@k8s-master ~]# kubectl create secret generic mysecret --from-env-file=env.txt
secret/mysecret created
[root@k8s-master ~]# kubectl get secret
NAME TYPE DATA AGE
mysecret Opaque 2 10s

清单式创建

  • 通过 YAML 配置文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#事先完成敏感数据的Base64编码
[root@k8s-master ~]# echo -n admin |base64
YWRtaW4=
[root@k8s-master ~]# echo -n 123456 |base64
MTIzNDU2

[root@k8s-master ~]# vim secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: mysecret
data:
username: YWRtaW4=
password: MTIzNDU2
[root@k8s-master ~]# kubectl apply -f secret.yaml
secret/mysecret created
[root@k8s-master ~]# kubectl get secret #查看存在的 secret,显示有2条数据
NAME TYPE DATA AGE
mysecret Opaque 2 8s
[root@k8s-master ~]# kubectl describe secret mysecret #查看数据的 Key
Name: mysecret
Namespace: default
Labels: <none>
Annotations:
Type: Opaque

Data
====
username: 5 bytes
password: 6 bytes
[root@k8s-master ~]# kubectl edit secret mysecret #查看具体的value,可以使用该命令
apiVersion: v1
data:
password: MTIzNDU2
username: YWRtaW4=
kind: Secret
metadata:
......
[root@k8s-master ~]# echo -n MTIzNDU2 |base64 --decode #通过 base64 将 Value 反编码:
123456
[root@k8s-master ~]# echo -n YWRtaW4= |base64 --decode
admin

如何使用Secret??

Pod 可以通过 Volume 或者环境变量的方式使用 Secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@k8s-master volumes]# vim pod-secret-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
spec:
containers:
- name: pod-secret
image: busybox
args:
- /bin/sh
- -c
- sleep 10;touch /tmp/healthy;sleep 30000
volumeMounts: #将 foo mount 到容器路径 /etc/foo,可指定读写权限为 readOnly。
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes: #定义 volume foo,来源为 secret mysecret。
- name: foo
secret:
secretName: mysecret
[root@k8s-master volumes]# kubectl apply -f pod-secret-demo.yaml
pod/pod-secret created
[root@k8s-master volumes]# kubectl get pods
pod-secret 1/1 Running 0 1m
[root@k8s-master volumes]# kubectl exec -it pod-secret sh
/ # ls /etc/foo/
password username
/ # cat /etc/foo/username
admin/ #
/ # cat /etc/foo/password
123456/ #

可以看到,Kubernetes 会在指定的路径 /etc/foo 下为每条敏感数据创建一个文件,文件名就是数据条目的 Key,这里是 /etc/foo/username 和 /etc/foo/password,Value 则以明文存放在文件中。
也可以自定义存放数据的文件名,比如将配置文件改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[root@k8s-master volumes]# cat pod-secret-demo.yaml 
apiVersion: v1
kind: Pod
metadata:
name: pod-secret
spec:
containers:
- name: pod-secret
image: busybox
args:
- /bin/sh
- -c
- sleep 10;touch /tmp/healthy;sleep 30000
volumeMounts:
- name: foo
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo
secret:
secretName: mysecret
items: #自定义存放数据的文件名
- key: username
path: my-secret/my-username
- key: password
path: my-secret/my-password
[root@k8s-master volumes]# kubectl delete pods pod-secret
pod "pod-secret" deleted
[root@k8s-master volumes]# kubectl apply -f pod-secret-demo.yaml
pod/pod-secret created
[root@k8s-master volumes]# kubectl exec -it pod-secret sh
/ # cat /etc/foo/my-secret/my-username
admin
/ # cat /etc/foo/my-secret/my-password
123456

这时数据将分别存放在 /etc/foo/my-secret/my-username 和 /etc/foo/my-secret/my-password 中。

以 Volume 方式使用的 Secret 支持动态更新:Secret 更新后,容器中的数据也会更新。

将 password 更新为 abcdef,base64 编码为 YWJjZGVm

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master ~]# vim secret.yaml 
apiVersion: v1
kind: Secret
metadata:
name: mysecret
data:
username: YWRtaW4=
password: YWJjZGVm
[root@k8s-master ~]# kubectl apply -f secret.yaml
secret/mysecret configured
/ # cat /etc/foo/my-secret/my-password
abcdef

通过 Volume 使用 Secret,容器必须从文件读取数据,会稍显麻烦,Kubernetes 还支持通过环境变量使用 Secret。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[root@k8s-master volumes]# cp pod-secret-demo.yaml pod-secret-env-demo.yaml
[root@k8s-master volumes]# vim pod-secret-env-demo.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-env
spec:
containers:
- name: pod-secret-env
image: busybox
args:
- /bin/sh
- -c
- sleep 10;touch /tmp/healthy;sleep 30000
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
[root@k8s-master volumes]# kubectl apply -f pod-secret-env-demo.yaml
pod/pod-secret-env created

[root@k8s-master volumes]# kubectl exec -it pod-secret-env sh
/ # echo $SECRET_USERNAME
admin
/ # echo $SECRET_PASSWORD
abcdef

通过环境变量 SECRET_USERNAME 和 SECRET_PASSWORD 成功读取到 Secret 的数据。
需要注意的是,环境变量读取 Secret 很方便,但无法支撑 Secret 动态更新。
Secret 可以为 Pod 提供密码、Token、私钥等敏感数据;对于一些非敏感数据,比如应用的配置信息,则可以用 ConfigMap。

8.2、ConifgMap解析

configmap是让配置文件从镜像中解耦,让镜像的可移植性和可复制性。许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。这些配置信息需要与docker image解耦,你总不能每修改一个配置就重做一个image吧?ConfigMap API给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。

ConfigMap API资源用来保存key-value pair配置数据,这个数据可以在pods里使用,或者被用来为像controller一样的系统组件存储配置数据。虽然ConfigMap跟Secrets类似,但是ConfigMap更方便的处理不含敏感信息的字符串。 注意:ConfigMaps不是属性配置文件的替代品。ConfigMaps只是作为多个properties文件的引用。可以把它理解为Linux系统中的/etc目录,专门用来存储配置文件的目录。下面举个例子,使用ConfigMap配置来创建Kuberntes Volumes,ConfigMap中的每个data项都会成为一个新文件。

1
2
3
4
5
6
7
8
9
[root@k8s-master volumes]# kubectl explain cm
KIND: ConfigMap
VERSION: v1
FIELDS:
apiVersion <string>
binaryData <map[string]string>
data <map[string]string>
kind <string>
metadata <Object>

ConfigMap创建方式

与 Secret 一样,ConfigMap 也支持四种创建方式:

  • 1、 通过 –from-literal:
    每个 –from-literal 对应一个信息条目。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master volumes]# kubectl create configmap nginx-config --from-literal=nginx_port=80 --from-literal=server_name=myapp.magedu.com
configmap/nginx-config created
[root@k8s-master volumes]# kubectl get cm
NAME DATA AGE
nginx-config 2 6s
[root@k8s-master volumes]# kubectl describe cm nginx-config
Name: nginx-config
Namespace: default
Labels: <none>
Annotations: <none>

Data
====
server_name:
----
myapp.magedu.com
nginx_port:
----
80
Events: <none>
  • 2、通过 –from-file:
    每个文件内容对应一个信息条目。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@k8s-master mainfests]# mkdir configmap && cd configmap
[root@k8s-master configmap]# vim www.conf
server {
server_name myapp.magedu.com;
listen 80;
root /data/web/html;
}
[root@k8s-master configmap]# kubectl create configmap nginx-www --from-file=./www.conf
configmap/nginx-www created
[root@k8s-master configmap]# kubectl get cm
NAME DATA AGE
nginx-config 2 3m
nginx-www 1 4s
[root@k8s-master configmap]# kubectl get cm nginx-www -o yaml
apiVersion: v1
data:
www.conf: "server {\n\tserver_name myapp.magedu.com;\n\tlisten 80;\n\troot /data/web/html;\n}\n"
kind: ConfigMap
metadata:
creationTimestamp: 2018-10-10T08:50:06Z
name: nginx-www
namespace: default
resourceVersion: "389929"
selfLink: /api/v1/namespaces/default/configmaps/nginx-www
uid: 7c3dfc35-cc69-11e8-801a-000c2972dc1f

如何使用configMap??

  • 1、环境变量方式注入到pod
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@k8s-master configmap]# vim pod-configmap.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-1
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/created-by: "cluster admin"
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
env:
- name: NGINX_SERVER_PORT
valueFrom:
configMapKeyRef:
name: nginx-config
key: nginx_port
- name: NGINX_SERVER_NAME
valueFrom:
configMapKeyRef:
name: nginx-config
key: server_name
[root@k8s-master configmap]# kubectl apply -f pod-configmap.yaml
pod/pod-cm-1 created
[root@k8s-master configmap]# kubectl exec -it pod-cm-1 -- /bin/sh
/ # echo $NGINX_SERVER_PORT
80
/ # echo $NGINX_SERVER_NAME
myapp.magedu.com

修改端口,可以发现使用环境变化注入pod中的端口不会根据配置的更改而变化

1
2
3
4
[root@k8s-master volumes]#  kubectl edit cm nginx-config
configmap/nginx-config edited
/ # echo $NGINX_SERVER_PORT
80
  • 2、存储卷方式挂载configmap:
    Volume 形式的 ConfigMap 也支持动态更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
[root@k8s-master configmap ~]# vim pod-configmap-2.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-2
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/created-by: "cluster admin"
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
volumeMounts:
- name: nginxconf
mountPath: /etc/nginx/config.d/
readOnly: true
volumes:
- name: nginxconf
configMap:
name: nginx-config
[root@k8s-master configmap ~]# kubectl apply -f pod-configmap-2.yaml
pod/pod-cm-2 created
[root@k8s-master configmap ~]# kubectl get pods
[root@k8s-master configmap ~]# kubectl exec -it pod-cm-2 -- /bin/sh
/ # cd /etc/nginx/config.d
/ # cat nginx_port
80
/ # cat server_name
myapp.magedu.com

[root@k8s-master configmap ~]# kubectl edit cm nginx-config #修改端口,再在容器中查看端口是否变化。
apiVersion: v1
data:
nginx_port: "800"
......

/ # cat nginx_port
800
[root@k8s-master configmap ~]# kubectl delete -f pod-configmap2.yaml
  • 3、以nginx-www配置nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
[root@k8s-master configmap ~]# vim pod-configmap3.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-cm-3
namespace: default
labels:
app: myapp
tier: frontend
annotations:
magedu.com/created-by: "cluster admin"
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v1
ports:
- name: http
containerPort: 80
volumeMounts:
- name: nginxconf
mountPath: /etc/nginx/conf.d/
readOnly: true
volumes:
- name: nginxconf
configMap:
name: nginx-www
[root@k8s-master configmap ~]# kubectl apply -f pod-configmap3.yaml
pod/pod-cm-3 created
[root@k8s-master configmap ~]# kubectl get pods
[root@k8s-master configmap]# kubectl exec -it pod-cm-3 -- /bin/sh
/ # cd /etc/nginx/conf.d/
/etc/nginx/conf.d # ls
www.conf
/etc/nginx/conf.d # cat www.conf
server {
server_name myapp.magedu.com;
listen 80;
root /data/web/html;
}

至此,K8S的存储卷到此结束!!!

k8s学习笔记-15-Ingress和Ingress Controller

[TOC]

一、什么是Ingress?

从前面的学习,我们可以了解到Kubernetes暴露服务的方式目前只有三种:LoadBlancer Service、ExternalName、NodePort Service、Ingress;而我们需要将集群内服务提供外界访问就会产生以下几个问题:

1、Pod 漂移问题

Kubernetes 具有强大的副本控制能力,能保证在任意副本(Pod)挂掉时自动从其他机器启动一个新的,还可以动态扩容等,通俗地说,这个 Pod 可能在任何时刻出现在任何节点上,也可能在任何时刻死在任何节点上;那么自然随着 Pod 的创建和销毁,Pod IP 肯定会动态变化;那么如何把这个动态的 Pod IP 暴露出去?这里借助于 Kubernetes 的 Service 机制,Service 可以以标签的形式选定一组带有指定标签的 Pod,并监控和自动负载他们的 Pod IP,那么我们向外暴露只暴露 Service IP 就行了;这就是 NodePort 模式:即在每个节点上开起一个端口,然后转发到内部 Pod IP 上,如下图所示:
此时的访问方式:http://nodeip:nodeport/
img

2、端口管理问题

采用 NodePort 方式暴露服务面临问题是,服务一旦多起来,NodePort 在每个节点上开启的端口会及其庞大,而且难以维护;这时,我们可以能否使用一个Nginx直接对内进行转发呢?众所周知的是,Pod与Pod之间是可以互相通信的,而Pod是可以共享宿主机的网络名称空间的,也就是说当在共享网络名称空间时,Pod上所监听的就是Node上的端口。那么这又该如何实现呢?简单的实现就是使用 DaemonSet 在每个 Node 上监听 80,然后写好规则,因为 Nginx 外面绑定了宿主机 80 端口(就像 NodePort),本身又在集群内,那么向后直接转发到相应 Service IP 就行了,如下图所示:
img

3、域名分配及动态更新问题

从上面的方法,采用 Nginx-Pod 似乎已经解决了问题,但是其实这里面有一个很大缺陷:当每次有新服务加入又该如何修改 Nginx 配置呢??我们知道使用Nginx可以通过虚拟主机域名进行区分不同的服务,而每个服务通过upstream进行定义不同的负载均衡池,再加上location进行负载均衡的反向代理,在日常使用中只需要修改nginx.conf即可实现,那在K8S中又该如何实现这种方式的调度呢???

假设后端的服务初始服务只有ecshop,后面增加了bbs和member服务,那么又该如何将这2个服务加入到Nginx-Pod进行调度呢?总不能每次手动改或者Rolling Update 前端 Nginx Pod 吧!!此时 Ingress 出现了,如果不算上面的Nginx,Ingress 包含两大组件:Ingress Controller 和 Ingress。
img

Ingress 简单的理解就是你原来需要改 Nginx 配置,然后配置各种域名对应哪个 Service,现在把这个动作抽象出来,变成一个 Ingress 对象,你可以用 yaml 创建,每次不要去改 Nginx 了,直接改 yaml 然后创建/更新就行了;那么问题来了:”Nginx 该怎么处理?”

Ingress Controller 这东西就是解决 “Nginx 的处理方式” 的;Ingress Controoler 通过与 Kubernetes API 交互,动态的去感知集群中 Ingress 规则变化,然后读取他,按照他自己模板生成一段 Nginx 配置,再写到 Nginx Pod 里,最后 reload 一下,工作流程如下图:
img

实际上Ingress也是Kubernetes API的标准资源类型之一,它其实就是一组基于DNS名称(host)或URL路径把请求转发到指定的Service资源的规则。用于将集群外部的请求流量转发到集群内部完成的服务发布。我们需要明白的是,Ingress资源自身不能进行“流量穿透”,仅仅是一组规则的集合,这些集合规则还需要其他功能的辅助,比如监听某套接字,然后根据这些规则的匹配进行路由转发,这些能够为Ingress资源监听套接字并将流量转发的组件就是Ingress Controller。

PS:Ingress 控制器不同于Deployment 控制器的是,Ingress控制器不直接运行为kube-controller-manager的一部分,它仅仅是Kubernetes集群的一个附件,类似于CoreDNS,需要在集群上单独部署。

二、如何创建Ingress资源

Ingress资源时基于HTTP虚拟主机或URL的转发规则,需要强调的是,这是一条转发规则。它在资源配置清单中的spec字段中嵌套了rules、backend和tls等字段进行定义。如下示例中定义了一个Ingress资源,其包含了一个转发规则:将发往myapp.magedu.com的请求,代理给一个名字为myapp的Service资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: extensions/v1beta1		
kind: Ingress
metadata:
name: ingress-myapp
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: myapp.magedu.com
http:
paths:
- path:
backend:
serviceName: myapp
servicePort: 80

Ingress 中的spec字段是Ingress资源的核心组成部分,主要包含以下3个字段:

  • rules:用于定义当前Ingress资源的转发规则列表;由rules定义规则,或没有匹配到规则时,所有的流量会转发到由backend定义的默认后端。
  • backend:默认的后端用于服务那些没有匹配到任何规则的请求;定义Ingress资源时,必须要定义backend或rules两者之一,该字段用于让负载均衡器指定一个全局默认的后端。
  • tls:TLS配置,目前仅支持通过默认端口443提供服务,如果要配置指定的列表成员指向不同的主机,则需要通过SNI TLS扩展机制来支持该功能。
    backend对象的定义由2个必要的字段组成:serviceName和servicePort,分别用于指定流量转发的后端目标Service资源名称和端口。
    rules对象由一系列的配置的Ingress资源的host规则组成,这些host规则用于将一个主机上的某个URL映射到相关后端Service对象,其定义格式如下:
1
2
3
4
5
6
7
8
9
spec:
rules:
- hosts: <string>
http:
paths:
- path:
backend:
serviceName: <string>
servicePort: <string>

需要注意的是,.spec.rules.host属性值,目前暂不支持使用IP地址定义,也不支持IP:Port的格式,该字段留空,代表着通配所有主机名。
tls对象由2个内嵌的字段组成,仅在定义TLS主机的转发规则上使用。

  • hosts: 包含 于 使用 的 TLS 证书 之内 的 主机 名称 字符串 列表, 因此, 此处 使用 的 主机 名 必须 匹配 tlsSecret 中的 名称。
  • secretName: 用于 引用 SSL 会话 的 secret 对象 名称, 在 基于 SNI 实现 多 主机 路 由 的 场景 中, 此 字段 为 可选。

三、Ingress资源类型

Ingress的资源类型有以下4种:

  • 1、单Service资源型Ingress
  • 2、基于URL路径进行流量转发
  • 3、基于主机名称的虚拟主机
  • 4、TLS类型的Ingress资源

1、单Service资源型Ingress

暴露单个服务的方法有多种,如NodePort、LoadBanlancer等等,当然也可以使用Ingress来进行暴露单个服务,只需要为Ingress指定default backend即可,如下示例:

1
2
3
4
5
6
7
8
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: my-ingress
spec:
backend:
serviceName: my-svc
servicePort: 80

Ingress控制器会为其分配一个IP地址接入请求流量,并将其转发至后端my-svc

四、Ingress Nginx部署

使用Ingress功能步骤:
1、安装部署ingress controller Pod
2、部署后端服务
3、部署ingress-nginx service
4、部署ingress

从前面的描述我们知道,Ingress 可以使用 yaml 的方式进行创建,从而得知 Ingress 也是标准的 K8S 资源,其定义的方式,也可以使用 explain 进行查看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@k8s-master ~]# kubectl explain ingress
KIND: Ingress
VERSION: extensions/v1beta1

DESCRIPTION:
Ingress is a collection of rules that allow inbound connections to reach
the endpoints defined by a backend. An Ingress can be configured to give
services externally-reachable urls, load balance traffic, terminate SSL,
offer name based virtual hosting etc.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

spec <Object>
Spec is the desired state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

status <Object>
Status is the current state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

1、部署Ingress controller

ingress-nginx在github上的地址
(1)下载ingress相关的yaml

1
2
3
4
5
6
7
8
9
10
11
12
[root@k8s-master ~]# mkdir ingress-nginx
[root@k8s-master ~]# cd ingress-nginx/
[root@k8s-master ingress-nginx]# for file in namespace.yaml configmap.yaml rbac.yaml tcp-services-configmap.yaml with-rbac.yaml udp-services-configmap.yaml default-backend.yaml;do wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/$file;done
[root@k8s-master ingress-nginx]# ll
total 28
-rw-r--r-- 1 root root 199 Sep 29 22:45 configmap.yaml #configmap用于为nginx从外部注入配置的
-rw-r--r-- 1 root root 1583 Sep 29 22:45 default-backend.yaml #配置默认后端服务
-rw-r--r-- 1 root root 69 Sep 29 22:45 namespace.yaml #创建独立的名称空间
-rw-r--r-- 1 root root 2866 Sep 29 22:45 rbac.yaml #rbac用于集群角色授权
-rw-r--r-- 1 root root 192 Sep 29 22:45 tcp-services-configmap.yaml
-rw-r--r-- 1 root root 192 Sep 29 22:45 udp-services-configmap.yaml
-rw-r--r-- 1 root root 2409 Sep 29 22:45 with-rbac.yaml

(2)创建ingress-nginx名称空间

1
2
3
4
5
6
7
8
9
[root@k8s-master ingress-nginx]# cat namespace.yaml 
apiVersion: v1
kind: Namespace
metadata:
name: ingress-nginx

---
[root@k8s-master ingress-nginx]# kubectl apply -f namespace.yaml
namespace/ingress-nginx created

(3)创建ingress controller的pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@k8s-master ingress-nginx]#  kubectl apply -f ./
configmap/nginx-configuration created
deployment.extensions/default-http-backend created
service/default-http-backend created
namespace/ingress-nginx configured
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
configmap/tcp-services created
configmap/udp-services created
deployment.extensions/nginx-ingress-controller created
[root@k8s-master ingress-nginx]# kubectl get pod -n ingress-nginx -w
NAME READY STATUS RESTARTS AGE
default-http-backend-7db7c45b69-gjrnl 0/1 ContainerCreating 0 35s
nginx-ingress-controller-6bd7c597cb-6pchv 0/1 ContainerCreating 0 34s

此处遇到一个问题,新版本的Kubernetes在安装部署中,需要从k8s.grc.io仓库中拉取所需镜像文件,但由于国内网络防火墙问题导致无法正常拉取。
docker.io仓库对google的容器做了镜像,可以通过下列命令下拉取相关镜像:

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-node01 ~]# docker pull mirrorgooglecontainers/defaultbackend-amd64:1.5
1.5: Pulling from mirrorgooglecontainers/defaultbackend-amd64
9ecb1e82bb4a: Pull complete
Digest: sha256:d08e129315e2dd093abfc16283cee19eabc18ae6b7cb8c2e26cc26888c6fc56a
Status: Downloaded newer image for mirrorgooglecontainers/defaultbackend-amd64:1.5

[root@k8s-node01 ~]# docker tag mirrorgooglecontainers/defaultbackend-amd64:1.5 k8s.gcr.io/defaultbackend-amd64:1.5
[root@k8s-node01 ~]# docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
mirrorgooglecontainers/defaultbackend-amd64 1.5 b5af743e5984 34 hours ago 5.13MB
k8s.gcr.io/defaultbackend-amd64 1.5 b5af743e5984 34 hours ago 5.13MB

2、部署后端服务

(1)查看ingress的配置清单选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[root@k8s-master ingress-nginx]# kubectl explain ingress.spec
KIND: Ingress
VERSION: extensions/v1beta1

RESOURCE: spec <Object>

DESCRIPTION:
Spec is the desired state of the Ingress. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

IngressSpec describes the Ingress the user wishes to exist.

FIELDS:
backend <Object> #定义后端有哪几个主机
A default backend capable of servicing requests that don't match any rule.
At least one of 'backend' or 'rules' must be specified. This field is
optional to allow the loadbalancer controller or defaulting logic to
specify a global default.

rules <[]Object> #定义规则
A list of host rules used to configure the Ingress. If unspecified, or no
rule matches, all traffic is sent to the default backend.

tls <[]Object>
TLS configuration. Currently the Ingress only supports a single TLS port,
443. If multiple members of this list specify different hosts, they will be
multiplexed on the same port according to the hostname specified through
the SNI TLS extension, if the ingress controller fulfilling the ingress
supports SNI.

(2)部署后端服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[root@k8s-master ingress-nginx]# cd ../mainfests/
[root@k8s-master mainfests]# mkdir ingress && cd ingress
[root@k8s-master ingress]# cp ../deploy-demo.yaml .
[root@k8s-master ingress]# vim deploy-demo.yaml
#创建service为myapp
apiVersion: v1
kind: Service
metadata:
name: myapp
namespace: default
spec:
selector:
app: myapp
release: canary
ports:
- name: http
targetPort: 80
port: 80
---
#创建后端服务的pod
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-backend-pod
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: myapp
release: canary
template:
metadata:
labels:
app: myapp
release: canary
spec:
containers:
- name: myapp
image: ikubernetes/myapp:v2
ports:
- name: http
containerPort: 80
[root@k8s-master ingress]# kubectl apply -f deploy-demo.yaml
service/myapp created
deployment.apps/myapp-backend-pod unchanged

(3)查看新建的后端服务pod

1
2
3
4
5
[root@k8s-master ingress]# kubectl get pods
NAME READY STATUS RESTARTS AGE
myapp-backend-pod-67f6f6b4dc-9jl9q 1/1 Running 0 7m
myapp-backend-pod-67f6f6b4dc-x5jsb 1/1 Running 0 7m
myapp-backend-pod-67f6f6b4dc-xzxbj 1/1 Running 0 7m

3、部署ingress-nginx service

通过ingress-controller对外提供服务,现在还需要手动给ingress-controller建立一个service,接收集群外部流量。方法如下:
(1)下载ingress-controller的yaml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@k8s-master ingress]# wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
[root@k8s-master ingress]# vim service-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
nodePort: 30080
- name: https
port: 443
targetPort: 443
protocol: TCP
nodePort: 30443
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx

---

(2)创建ingress-controller的service,并测试访问

1
2
3
4
5
6
[root@k8s-master ingress]# kubectl apply -f service-nodeport.yaml 
service/ingress-nginx created
[root@k8s-master ingress]# kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-http-backend ClusterIP 10.104.41.201 <none> 80/TCP 45m
ingress-nginx NodePort 10.96.135.79 <none> 80:30080/TCP,443:30443/TCP 11s

此时访问:192.168.56.12:30080
此时应该是404 ,调度器是正常工作的,但是后端服务没有关联
img

4、部署ingress

(1)编写ingress的配置清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[root@k8s-master ingress]# vim ingress-myapp.yaml
apiVersion: extensions/v1beta1 #api版本
kind: Ingress #清单类型
metadata: #元数据
name: ingress-myapp #ingress的名称
namespace: default #所属名称空间
annotations: #注解信息
kubernetes.io/ingress.class: "nginx"
spec: #规格
rules: #定义后端转发的规则
- host: myapp.magedu.com #通过域名进行转发
http:
paths:
- path: #配置访问路径,如果通过url进行转发,需要修改;空默认为访问的路径为"/"
backend: #配置后端服务
serviceName: myapp
servicePort: 80
[root@k8s-master ingress]# kubectl apply -f ingress-myapp.yaml
[root@k8s-master ingress]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-myapp myapp.magedu.com 80 46s

(2)查看ingress-myapp的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@k8s-master ingress]# kubectl describe ingress ingress-myapp
Name: ingress-myapp
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
myapp.magedu.com
myapp:80 (<none>)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-myapp","namespace":"default"},"spec":{"rules":[{"host":"myapp.magedu.com","http":{"paths":[{"backend":{"serviceName":"myapp","servicePort":80},"path":null}]}}]}}

kubernetes.io/ingress.class: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 1m nginx-ingress-controller Ingress default/ingress-myapp

[root@k8s-master ingress]# kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
default-http-backend-7db7c45b69-fndwp 1/1 Running 0 31m
nginx-ingress-controller-6bd7c597cb-6pchv 1/1 Running 0 55m

(3)进入nginx-ingress-controller进行查看是否注入了nginx的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[root@k8s-master ingress]# kubectl exec -n ingress-nginx -it nginx-ingress-controller-6bd7c597cb-6pchv -- /bin/bash
www-data@nginx-ingress-controller-6bd7c597cb-6pchv:/etc/nginx$ cat nginx.conf
......
## start server myapp.magedu.com
server {
server_name myapp.magedu.com ;

listen 80;

set $proxy_upstream_name "-";

location / {

set $namespace "default";
set $ingress_name "ingress-myapp";
set $service_name "myapp";
set $service_port "80";
set $location_path "/";

rewrite_by_lua_block {

balancer.rewrite()

}

log_by_lua_block {

balancer.log()

monitor.call()
}
......

(4)修改本地host文件,进行访问
192.168.56.12 myapp.magedu.com
192.168.56.13 myapp.magedu.com
img

四、增加tomcat服务

(1)编写tomcat的配置清单文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
[root@k8s-master ingress]# cp deploy-demo.yaml tomcat-demo.yaml
[root@k8s-master ingress]# vim tomcat-demo.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat
namespace: default
spec:
selector:
app: tomcat
release: canary
ports:
- name: http
targetPort: 8080
port: 8080
- name: ajp
targetPort: 8009
port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
namespace: default
spec:
replicas: 3
selector:
matchLabels:
app: tomcat
release: canary
template:
metadata:
labels:
app: tomcat
release: canary
spec:
containers:
- name: tomcat
image: tomcat:8.5.34-jre8-alpine
#此镜像在dockerhub上进行下载,需要查看版本是否有变化,hub.docker.com
ports:
- name: http
containerPort: 8080
name: ajp
containerPort: 8009
[root@k8s-master ingress]# kubectl get pods
NAME READY STATUS RESTARTS AGE
tomcat-deploy-6dd558cd64-b4xbm 1/1 Running 0 3m
tomcat-deploy-6dd558cd64-qtwpx 1/1 Running 0 3m
tomcat-deploy-6dd558cd64-w7f9s 1/1 Running 0 5m

(2)进入tomcat的pod中进行查看是否监听8080和8009端口,并查看tomcat的svc

1
2
3
4
5
6
7
8
9
10
11
[root@k8s-master ingress]# kubectl exec tomcat-deploy-6dd558cd64-b4xbm -- netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.1:8005 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:8009 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN

[root@k8s-master ingress]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
......
tomcat ClusterIP 10.104.158.148 <none> 8080/TCP,8009/TCP 28m

(3)编写tomcat的ingress规则,并创建ingress资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master ingress]# cp ingress-myapp.yaml ingress-tomcat.yaml
[root@k8s-master ingress]# vim ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: tomcat
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
rules:
- host: tomcat.magedu.com #主机域名
http:
paths:
- path:
backend:
serviceName: tomcat
servicePort: 8080
[root@k8s-master ingress]# kubectl apply -f ingress-tomcat.yaml
ingress.extensions/tomcat created

(4)查看ingress具体信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
[root@k8s-master ingress]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-myapp myapp.magedu.com 80 3h
tomcat tomcat.magedu.com 80 5s
[root@k8s-master ingress]# kubectl describe ingress
Name: ingress-myapp
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
myapp.magedu.com
myapp:80 (<none>)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-myapp","namespace":"default"},"spec":{"rules":[{"host":"myapp.magedu.com","http":{"paths":[{"backend":{"serviceName":"myapp","servicePort":80},"path":null}]}}]}}

kubernetes.io/ingress.class: nginx
Events: <none>


Name: tomcat
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
Rules:
Host Path Backends
---- ---- --------
tomcat.magedu.com
tomcat:8080 (<none>)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"tomcat","namespace":"default"},"spec":{"rules":[{"host":"tomcat.magedu.com","http":{"paths":[{"backend":{"serviceName":"tomcat","servicePort":8080},"path":null}]}}]}}

kubernetes.io/ingress.class: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 2m nginx-ingress-controller Ingress default/tomcat

(5)测试访问:tomcat.mageud.com:30080
img

(6)总结
从前面的部署过程中,可以再次进行总结部署的流程如下:
①下载Ingress-controller相关的YAML文件,并给Ingress-controller创建独立的名称空间;
②部署后端的服务,如myapp,并通过service进行暴露;
③部署Ingress-controller的service,以实现接入集群外部流量;
④部署Ingress,进行定义规则,使Ingress-controller和后端服务的Pod组进行关联。
本次部署后的说明图如下:
img

四、构建TLS站点

(1)准备证书

1
2
3
4
5
6
7
[root@k8s-master ingress]# openssl genrsa -out tls.key 2048 
Generating RSA private key, 2048 bit long modulus
.......+++
.......................+++
e is 65537 (0x10001)

[root@k8s-master ingress]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=Beijing/L=Beijing/O=DevOps/CN=tomcat.magedu.com

(2)生成secret

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@k8s-master ingress]# kubectl create secret tls tomcat-ingress-secret --cert=tls.crt --key=tls.key
secret/tomcat-ingress-secret created
[root@k8s-master ingress]# kubectl get secret
NAME TYPE DATA AGE
default-token-j5pf5 kubernetes.io/service-account-token 3 39d
tomcat-ingress-secret kubernetes.io/tls 2 9s
[root@k8s-master ingress]# kubectl describe secret tomcat-ingress-secret
Name: tomcat-ingress-secret
Namespace: default
Labels: <none>
Annotations: <none>

Type: kubernetes.io/tls

Data
====
tls.crt: 1294 bytes
tls.key: 1679 bytes

(3)创建ingress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[root@k8s-master ingress]# kubectl explain ingress.spec
[root@k8s-master ingress]# kubectl explain ingress.spec.tls
[root@k8s-master ingress]# cp ingress-tomcat.yaml ingress-tomcat-tls.yaml
[root@k8s-master ingress]# vim ingress-tomcat-tls.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat-tls
namespace: default
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls:
- hosts:
- tomcat.magedu.com
secretName: tomcat-ingress-secret
rules:
- host: tomcat.magedu.com
http:
paths:
- path:
backend:
serviceName: tomcat
servicePort: 8080

[root@k8s-master ingress]# kubectl apply -f ingress-tomcat-tls.yaml
ingress.extensions/ingress-tomcat-tls created
[root@k8s-master ingress]# kubectl get ingress
NAME HOSTS ADDRESS PORTS AGE
ingress-myapp myapp.magedu.com 80 4h
ingress-tomcat-tls tomcat.magedu.com 80, 443 5s
tomcat tomcat.magedu.com 80 1h
[root@k8s-master ingress]# kubectl describe ingress ingress-tomcat-tls
Name: ingress-tomcat-tls
Namespace: default
Address:
Default backend: default-http-backend:80 (<none>)
TLS:
tomcat-ingress-secret terminates tomcat.magedu.com
Rules:
Host Path Backends
---- ---- --------
tomcat.magedu.com
tomcat:8080 (<none>)
Annotations:
kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"nginx"},"name":"ingress-tomcat-tls","namespace":"default"},"spec":{"rules":[{"host":"tomcat.magedu.com","http":{"paths":[{"backend":{"serviceName":"tomcat","servicePort":8080},"path":null}]}}],"tls":[{"hosts":["tomcat.magedu.com"],"secretName":"tomcat-ingress-secret"}]}}

kubernetes.io/ingress.class: nginx
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal CREATE 20s nginx-ingress-controller Ingress default/ingress-tomcat-tls

(4)访问测试:https://tomcat.magedu.com:30443

k8s学习笔记-13-Pod控制器--DaemonSet

[TOC]

一、什么是DaemonSet?

DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。

使用 DaemonSet 的一些典型用法:

  • 运行集群存储 daemon,例如在每个 Node 上运行 glusterdceph
  • 在每个 Node 上运行日志收集 daemon,例如fluentdlogstash
  • 在每个 Node 上运行监控 daemon,例如 Prometheus Node Exportercollectd、Datadog 代理、New Relic 代理,或 Ganglia gmond

一个简单的用法是,在所有的 Node 上都存在一个 DaemonSet,将被作为每种类型的 daemon 使用。 一个稍微复杂的用法可能是,对单独的每种类型的 daemon 使用多个 DaemonSet,但具有不同的标志,和/或对不同硬件类型具有不同的内存、CPU要求。

二、编写DaemonSet Spec

(1)必需字段

和其它所有 Kubernetes 配置一样,DaemonSet 需要 apiVersionkindmetadata字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[root@k8s-master ~]# kubectl explain daemonset
KIND: DaemonSet
VERSION: extensions/v1beta1

DESCRIPTION:
DEPRECATED - This group version of DaemonSet is deprecated by
apps/v1beta2/DaemonSet. See the release notes for more information.
DaemonSet represents the configuration of a daemon set.

FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an
object. Servers should convert recognized schemas to the latest internal
value, and may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata

spec <Object>
The desired behavior of this daemon set. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

status <Object>
The current status of this daemon set. This data may be out of date by some
window of time. Populated by the system. Read-only. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status

(2)Pod模板

.spec 唯一必需的字段是 .spec.template

.spec.template 是一个 Pod 模板。 它与 Pod 具有相同的 schema,除了它是嵌套的,而且不具有 apiVersionkind 字段。

Pod 除了必须字段外,在 DaemonSet 中的 Pod 模板必须指定合理的标签(查看 pod selector)。

在 DaemonSet 中的 Pod 模板必需具有一个值为 AlwaysRestartPolicy,或者未指定它的值,默认是 Always

1
[root@k8s-master ~]# kubectl explain daemonset.spec.template.spec

(3)Pod Seletor

.spec.selector 字段表示 Pod Selector,它与 Job 或其它资源的 .sper.selector 的原理是相同的。

spec.selector 表示一个对象,它由如下两个字段组成:

  • matchLabels - 与 ReplicationController.spec.selector 的原理相同。
  • matchExpressions - 允许构建更加复杂的 Selector,可以通过指定 key、value 列表,以及与 key 和 value 列表的相关的操作符。

当上述两个字段都指定时,结果表示的是 AND 关系。

如果指定了 .spec.selector,必须与 .spec.template.metadata.labels 相匹配。如果没有指定,它们默认是等价的。如果与它们配置的不匹配,则会被 API 拒绝。

如果 Pod 的 label 与 selector 匹配,或者直接基于其它的 DaemonSet、或者 Controller(例如 ReplicationController),也不可以创建任何 Pod。 否则 DaemonSet Controller 将认为那些 Pod 是它创建的。Kubernetes 不会阻止这样做。一个场景是,可能希望在一个具有不同值的、用来测试用的 Node 上手动创建 Pod。

(4)Daemon Pod通信

与 DaemonSet 中的 Pod 进行通信,几种可能的模式如下:

  • Push:配置 DaemonSet 中的 Pod 向其它 Service 发送更新,例如统计数据库。它们没有客户端。
  • NodeIP 和已知端口:DaemonSet 中的 Pod 可以使用 hostPort,从而可以通过 Node IP 访问到 Pod。客户端能通过某种方法知道 Node IP 列表,并且基于此也可以知道端口。
  • DNS:创建具有相同 Pod Selector 的 Headless Service,然后通过使用 endpoints 资源或从 DNS 检索到多个 A 记录来发现 DaemonSet。
  • Service:创建具有相同 Pod Selector 的 Service,并使用该 Service 访问到某个随机 Node 上的 daemon。(没有办法访问到特定 Node)

三、创建redis-filebeat的DaemonSet演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
(1)编辑daemonSet的yaml文件
可以在同一个yaml文件中定义多个资源,这里将redis和filebeat定在一个文件当中

[root@k8s-master mainfests]# vim ds-demo.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: redis
role: logstor
template:
metadata:
labels:
app: redis
role: logstor
spec:
containers:
- name: redis
image: redis:4.0-alpine
ports:
- name: redis
containerPort: 6379
---
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: filebeat-ds
namespace: default
spec:
selector:
matchLabels:
app: filebeat
release: stable
template:
metadata:
labels:
app: filebeat
release: stable
spec:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.5-alpine
env:
- name: REDIS_HOST
value: redis.default.svc.cluster.local
- name: REDIS_LOG_LEVEL
value: info

(2)创建pods
[root@k8s-master mainfests]# kubectl apply -f ds-demo.yaml
deployment.apps/redis created
daemonset.apps/filebeat-ds created

(3)暴露端口
[root@k8s-master mainfests]# kubectl expose deployment redis --port=6379
service/redis exposed
[root@k8s-master mainfests]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 16d
myapp NodePort 10.106.67.242 <none> 80:32432/TCP 13d
nginx ClusterIP 10.106.162.254 <none> 80/TCP 14d
redis ClusterIP 10.107.163.143 <none> 6379/TCP 4s

[root@k8s-master mainfests]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-rpp9p 1/1 Running 0 5m
filebeat-ds-vwx7d 1/1 Running 0 5m
pod-demo 2/2 Running 6 5d
redis-5b5d6fbbbd-v82pw 1/1 Running 0 36s

(4)测试redis是否收到日志
[root@k8s-master mainfests]# kubectl exec -it redis-5b5d6fbbbd-v82pw -- /bin/sh
/data # netstat -tnl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:6379 0.0.0.0:* LISTEN
tcp 0 0 :::6379 :::* LISTEN

/data # nslookup redis.default.svc.cluster.local
nslookup: can't resolve '(null)': Name does not resolve

Name: redis.default.svc.cluster.local
Address 1: 10.107.163.143 redis.default.svc.cluster.local

/data # redis-cli -h redis.default.svc.cluster.local
redis.default.svc.cluster.local:6379> KEYS *  #由于redis在filebeat后面才启动,日志可能已经发走了,所以查看key为空
(empty list or set)

[root@k8s-master mainfests]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-rpp9p 1/1 Running 0 14m
filebeat-ds-vwx7d 1/1 Running 0 14m
pod-demo 2/2 Running 6 5d
redis-5b5d6fbbbd-v82pw 1/1 Running 0 9m
[root@k8s-master mainfests]# kubectl exec -it filebeat-ds-rpp9p -- /bin/sh
/ # cat /etc/filebeat/filebeat.yml
filebeat.registry_file: /var/log/containers/filebeat_registry
filebeat.idle_timeout: 5s
filebeat.spool_size: 2048

logging.level: info

filebeat.prospectors:
- input_type: log
paths:
- "/var/log/containers/*.log"
- "/var/log/docker/containers/*.log"
- "/var/log/startupscript.log"
- "/var/log/kubelet.log"
- "/var/log/kube-proxy.log"
- "/var/log/kube-apiserver.log"
- "/var/log/kube-controller-manager.log"
- "/var/log/kube-scheduler.log"
- "/var/log/rescheduler.log"
- "/var/log/glbc.log"
- "/var/log/cluster-autoscaler.log"
symlinks: true
json.message_key: log
json.keys_under_root: true
json.add_error_key: true
multiline.pattern: '^\s'
multiline.match: after
document_type: kube-logs
tail_files: true
fields_under_root: true

output.redis:
hosts: ${REDIS_HOST:?No Redis host configured. Use env var REDIS_HOST to set host.}
key: "filebeat"

[root@k8s-master mainfests]# kubectl get pods -l app=filebeat -o wide
NAME READY STATUS RESTARTS AGE IP NODE
filebeat-ds-rpp9p 1/1 Running 0 16m 10.244.2.12 k8s-node02
filebeat-ds-vwx7d 1/1 Running 0 16m 10.244.1.15 k8s-node01

四、DaemonSet的滚动更新

DaemonSet有两种更新策略类型:

  • OnDelete:这是向后兼容性的默认更新策略。使用 OnDelete更新策略,在更新DaemonSet模板后,只有在手动删除旧的DaemonSet pod时才会创建新的DaemonSet pod。这与Kubernetes 1.5或更早版本中DaemonSet的行为相同。
  • RollingUpdate:使用RollingUpdate更新策略,在更新DaemonSet模板后,旧的DaemonSet pod将被终止,并且将以受控方式自动创建新的DaemonSet pod。

要启用DaemonSet的滚动更新功能,必须将其设置 .spec.updateStrategy.typeRollingUpdate

(1)查看当前的更新策略:

1
2
[root@k8s-master mainfests]# kubectl get ds/filebeat-ds -o go-template='{{.spec.updateStrategy.type}}{{"\n"}}'
RollingUpdate

(2)更新DaemonSet模板

RollingUpdateDaemonSet的任何更新都.spec.template将触发滚动更新。这可以通过几个不同的kubectl命令来完成。

声明式命令方式:

如果使用配置文件进行更新DaemonSet,可以使用kubectl aapply:

1
kubectl apply -f ds-demo.yaml

补丁式命令方式:

1
2
3
kubectl edit ds/filebeat-ds

kubectl patch ds/filebeat-ds -p=<strategic-merge-patch>

仅仅更新容器镜像还可以使用以下命令:

1
kubectl set image ds/<daemonset-name> <container-name>=<container-new-image>

下面对filebeat-ds的镜像进行版本更新,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@k8s-master mainfests]# kubectl set image daemonsets filebeat-ds filebeat=ikubernetes/filebeat:5.6.6-alpine
daemonset.extensions/filebeat-ds image updated

[root@k8s-master mainfests]# kubectl get pods -w  #观察滚动更新状态
NAME READY STATUS RESTARTS AGE
filebeat-ds-rpp9p 1/1 Running 0 27m
filebeat-ds-vwx7d 0/1 Terminating 0 27m
pod-demo 2/2 Running 6 5d
redis-5b5d6fbbbd-v82pw 1/1 Running 0 23m
filebeat-ds-vwx7d 0/1 Terminating 0 27m
filebeat-ds-vwx7d 0/1 Terminating 0 27m
filebeat-ds-s466l 0/1 Pending 0 0s
filebeat-ds-s466l 0/1 ContainerCreating 0 0s
filebeat-ds-s466l 1/1 Running 0 13s
filebeat-ds-rpp9p 1/1 Terminating 0 28m
filebeat-ds-rpp9p 0/1 Terminating 0 28m
filebeat-ds-rpp9p 0/1 Terminating 0 28m
filebeat-ds-rpp9p 0/1 Terminating 0 28m
filebeat-ds-hxgdx 0/1 Pending 0 0s
filebeat-ds-hxgdx 0/1 ContainerCreating 0 0s
filebeat-ds-hxgdx 1/1 Running 0 28s

[root@k8s-master mainfests]# kubectl get pods
NAME READY STATUS RESTARTS AGE
filebeat-ds-hxgdx 1/1 Running 0 2m
filebeat-ds-s466l 1/1 Running 0 2m
pod-demo 2/2 Running 6 5d
redis-5b5d6fbbbd-v82pw 1/1 Running 0 25m

从上面的滚动更新,可以看到在更新过程中,是先终止旧的pod,再创建一个新的pod,逐步进行替换的,这就是DaemonSet的滚动更新策略!