K8s备忘
k8s常用命令
… … … kubectl … … … ∞
#查看命名空间nsA下pod1的日志最近100行 |
… … … deployment … … … ∞
# 创建并记录 --record参数可以记录命令,可以方便的查看每次revision的变化 |
… … … ipvs … … … ∞
ipvsadm -Ln
… … … docker … … … ∞
# 一键删除全部容器 |
K8s组件
高可用集群的高可用节点数一般是3 5 7 9,避免双数高可用集群副本数最好是>=3 奇数个
Master的组件
- APIServer:所有服务访问统一入口
- ControllerManager控制器:维持副本期望数
- Scheduler调度器:负责接收任务,选择合适的节点进行分配任务
- ETCD键值对数据库:存储K8s集群所有重要信息(持久化),官方将它定位成一个可信赖的分布式键值存储服务,能够为整个分布式集群存储一些关键数据,协助分布式集群的正常运转ETCDv2直接存储到内存(已在k8sv1.11中弃用),v3引入本地卷的持久化(1.11前不存在意味着关机不会有损坏)
Node的组件
- Kubelet:维持pod生命周期直接跟容器引擎(docker)交互实现容器的生命周期管理
- Kube-proxy:实现pod间访问、负载均衡,默认操作防火墙实现pod映射,新版ipvs负责写入规则至IPTables、IPVS 实现服务映射访问
其他插件
- CoreDNS:可以为集群中的SVC创建一个域名IP的对应关系解析,实现负载均衡的其一
- Dashboard:给K8s集群提供一个B/S结构访问体系
- Ingress Controller:官方只能实现四层代理,Ingress可以实现七层
- Federation:提供一个可以跨集群中心多K8s统一管理功能
- Prometheus:提供一个K8s集群的监控能力
- ELK:提供K8s集群日志统一分析接入平台
Pod
分类
自主式Pod:Pod退出了,此类Pod不会被创建
控制器管理的Pod:在控制器的生命周期里,始终要维持Pod的副本数目
pause:只要有pod,此容器就会被启动,容器网络栈
资源清单
资源
K8s中所有的内容都抽象为资源,资源实例化之后,叫做对象
集群资源分类
- 名称空间级别:仅ns内可见
- 工作负载型资源(workload):Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、CronJob、ReplicationController(在v1.11版本被废弃)
- 服务发现及负载均衡型资源(ServiceDiscovery LoadBalance):Service、Ingress
- 配置与存储型资源:Volume(存储卷)、CSI(容器存储接口,可扩展各种各样的第三方存储卷)
- 特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)
- 集群级别:全集群可见(role)
- Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding
- 元数据型:通过指标进行操作(HPA)
- HPA、PodTemplate、LimitRange
资源清单YAML
yaml文件就是资源清单
yaml意思: 仍是一种标记语言,但为了强调这种语言以数据为中心,而不是以标记语言为重点基本语法:
缩进只能用空格,不能用tab
缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
注释用#
yaml支持的数据结构:
对象:键值对集合,又称为映射(mapping)/哈希(hashes)/字典(dictionary)
数组:一组按次序排列的值,又称为序列(sequence)/列表(list)
纯量(scalars):单个的、不可再分的值
- 对象类型:对象的一组键值对,使用冒号结构表示
name: jade
age: 18
# yaml也允许另一个写法,写成一个行内对象
hash: { name: jade, age: 18 } - 数组类型:一组连词线开头的行,构成一个数组
animal
- Cat
- Dog
# 数组也可以采用行内表示法
animal: [Cat, Dog] - 复合结构:对象和数组可以结合使用,形成复合结构
languages:
- Ruby
- Perl
- Python
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Python: python.org
Perl: http://use.perl.org - 纯量:纯量是最基本的、不可再分的值
1.string boolean integer float Null
2.时间 日期
## integer:直接以字面量的形式表示
number: 12.30
## boolean:用true false表示
## null:用~表示,或者不写也表示null
## 时间采用ISO8601格式
iso8601: 2001-12-14t21:59:43.10-05:00
## 日期采用复合iso8601格式的年、月、日表示
date: 1976-07-31
## yaml允许使用!!强制转换数据类型
e: !!str 123
f: !!str true
## 字符串:默认不使用引号表示
# 包含空格或特殊字符需放在引号中:
str: '内容: 字符串'
# 单引号、双引号都可使用,双银不会对特殊字符转义:
s1: '内容\n字符串'
s2: "内容\n字符串"
# 单引号中还有单引号,需用另一个单引号转义:
str: 'labor''s day'
# 字符串可写成多行,从第二行开始必须有一个但空格缩进,换行符会被转换为空格
str: 这是一段
多行
字符串
# 多行字符串可以使用|保留换行符,也可以用>折叠换行
this: |
Foo
Bar
that: >
Foo
Bar
+ 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行
s1: |
Foo
s1: |+
Foo
s1: |-
Foo
```
#### 常用字段说明
```bash
kubectl explain <resources> 可查看模板信息,required表示必需字段
kubectl explain pod
kubectl explain pod.api - 必须存在的对象
变量名 | 类型 | 作用 |
---|---|---|
verison | String | 指k8s API版本,目前是v1,可使用kubectl api-versions命令查看 |
kind | String | 指yaml文件定义的资源类型和角色,如Pod |
metadata | Object | 元数据对象,固定值 |
metadata.name | String | 元数据对象的名字,自定义,比如命名Pod的名字 |
metadata.namespace | String | 元数据对象的命名空间,默认default,可自定义 |
Spec | Object | 详细定义对象,固定值 |
spec.containers[] | list | Spec对象的容器列表定义 |
spec.containers[].name | String | 定义容器的名字 |
spec.containers[].image | String | 定义要用到的镜像名称 |
- 主要对象,不写也可,会有默认值
变量名 | 类型 | 作用 |
---|---|---|
spec.containers[].name | String | 定义容器的名字,默认随机 |
spec.containers[].image | String | 定义要用到的镜像名称 |
spec.containers[].imagePullPolicy | String | 定义镜像拉去策略,可选Always(默认值,每次都尝试重新拉取镜像)、Never(仅使用本地镜像)、IfNotPresent(如果本地有镜像就是用本地镜像,没有就拉取在线镜像) |
spec.containers[].command[] | list | 指定容器启动命令,因为是数组,可以指定多个,不指定则使用镜像打包时使用的启动命令 |
spec.containers[].args[] | list | 指定容器启动命令参数,因为是数组可以指定多个 |
spec.containers[].workingDir | string | 指定容器的工作目录 |
spec.containers[].env[].name | string | 指定环境变量名称 |
spec.containers[].env[].value | string | 指定环境变量值 |
spec.containers[].resources | object | 指定资源限制和资源请求的值(设置容器的资源上限) |
spec.containers[].resources.limits | object | 指定设置容器运行时资源的运行上限 |
spec.containers[].resources.limits.cpu | string | 指定cpu的限制,单位为core数,将用于docker run –cpu-shares参数 |
spec.containers[].resources.limits.memory | string | 指定MEM内存的限制,单位MiB、GiB |
spec.containers[].resources.requests | object | 指定容器启动和调度时的限制设置 |
spec.containers[].resources.requests.cpu | string | cpu请求,单位为core数,容器启动时初始化可用数量 |
spec.containers[].resources.requests.memory | string | 内存请求,单位为MiB、GiB,容器启动时初始化可用数量 |
- 额外的参数项spec.restartPolicy
变量名 | 类型 | 作用 |
---|---|---|
spec.restartPolicy | string | 定义Pod的重启策略,可选值为Always(默认值,Pod一旦终止运行,则无论容器是如何终止的,kubelet都将重启它(重启多次将意外退出))、OnFailure(只有Pod以非0退出码终止时,kubelet才会重启它;如果正常退出(退出码为0),kubelet则不会重启它)、Never(Pod终止后,kubelet将退出码报告给master,不会重启该Pod) |
spec.nodeSelector | object | 定义Node的Label过滤标签,以key:value格式指定 |
spec.imagePullSecrets | object | 定义pull镜像时使用secret名称,以name:secretkey格式指定 |
spec.hostNetwork | boolean | 定义是否使用主机网络模式,默认false,设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本 |
容器生命周期
Pod能具有多个容器,应用运行在容器里,但是也有可能有一个或多个先于应用容器启动的Init容器
Init容器与普通容器非常像,除了如下两点
Init容器总是运行到成功完成为止
每个Init容器都必须在下一个Init容器启动之前成功完成
如果Pod的Init容器失败,Kubernetes会不断地重启该Pod,知道Init容器成功为止,但是如果Pod对应的restartPolicy为Never,它不会重新启动
Init容器启动相关代码优势:(因为Init容器具有与应用程序容器分离的单独镜像,所以有如下优势)
Init容器可以包含并运行实用工具(出于安全考虑,不建议在应用程序容器镜像中包含这些实用工具)
Init容器可以包含使用工具和定制化代码来安装(但是不能出现在应用程序镜像中。例如创建镜像没必要from另一个镜像,会造成MainC的冗余,只需在安装过程中使用类似sed、sek、python或dig这样的工具)
应用程序镜像可以分离出创建和部署的角色,而没有必要联合他们构建一个单独的镜像
Init容器使用Linux Namespace,所以相对应用程序容器来说具有不通的文件系统视图,因此,他们能够具有访问Secret的权限,而应用程序则不能
Init容器必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以Init容器能够提供一种简单的阻塞或延迟应用容器启动的方法,直到满足了一组先决条件
Init容器的特殊说明
在Pod启动过程中,Init容器会按顺序在 网络和数据卷初始化(pause中完成)之后启动,每个容器必须在下一个容器启动之前成功退出
如果由于运行时或失败退出,将导致容器启动失败,它会根据Pod的restartPolicy指定的策略进行重试。如果Pod的restartPolicy设置为Always,Init容器失败时会使用RestartPolicy策略
在所有的Init容器没有成功之前,Pod将不会变成Ready状态。Init容器的端口将不会在Service中进行聚集。正在初始化中的Pod处于Pending状态,但应该会将Initializing状态设置为true
如果Pod重启,所有Init容器必须重新执行
对Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效,更改Init容器的image字段,等价于重启该Pod
Init容器具有应用容器的所有字段,除了readinessProde,因为Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态,这会在验证过程中强制执行
在Pod中的每个app和init容器的名称必须唯一,与任何其它容器共享同一个名称,会在验证时抛出错误
start stop
Pod phase(相位) 可能存在的值
挂起(Pending):Pod已被k8s系统接受,但有1个或多个容器镜像尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间
运行中(Running):该Pod已经绑定到了1个Node上,Pod中所有的容器都已被创建。至少有1个容器正在运行或处于启动或重启状态
成功(Succeeded):Pod中所有容器都被成功终止,并且不会再重启(常出现在job cronjob中,以0状态退出)
失败(Failed):Pod中所有容器都已终止,并且至少有1个容器是因为失败终止(以非0状态退出或被系统终止)
未知(Unknown):因为某些原因无法取得Pod的状态,通常是因为与Pod所在主机通信失败
探针
探针是由kubelet对容器执行的定期诊断
要执行诊断,kubelet调用由容器实现的Handler,有三种类型的处理程序:
ExecAction:在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功
TCPSocketAction:对指定端口上的容器的IP地址进行TCP检查,如果端口打开,则诊断被认为是成功的
HTTPGetAction:对指定的端口和路径上的容器的IP地址执行HTTPGet请求,如果200≤响应的状态码<400(2xx成功 3xx跳转),则诊断被认为是成功的
探测结果:每次探测都将获得以下三种结果之一:
- 成功Success:容器通过了诊断
- 失败Failure:容器未通过诊断
- 未知Unknown:诊断失败,因此不会采取任何行动
探测方式:
- livenessProbe:指示容器是否正在运行。如果存活探针失败,则kubelet会杀死容器,并且容器将受到其 重启策略 的影响。如果容器不提供存活探针,则默认状态为Success
- readinessProbe:指示容器是否准备好服务请求。如果就绪探针失败,端口控制器将从与Pod匹配的所有Service的端点中删除该Pod的IP地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success
Pod控制器(controller,相当于一个状态机用来控制Pod的具体状态和行为)
ReplicationController
用来确保容器应用的副本数始终保持在用户定义的副本数
如果有容器异常退出,会自动创建新的Pod来替代;如果有异常多出来的,会自动回收
新版k8s中建议用rs代替rc
ReplicaSet
和rc没有本质的不通,只是名字不一样,但rs支持集合式的selector
虽然rs可独立使用,但建议用deploy自动管理rs,这样无需担心与其他机制不兼容问题(比如rs不支持rolling-update,但deploy支持)
Deployment
不负责pod的创建,deploy定义rs,rs创建pod
- 更新策略
- 更新的时候停用旧rs,创建新rs
- deploy可以保证在升级时只有一定数量的pod是down的,默认会确保至少有比期望pod数量少一个是up状态(最多一个不可用)
- 同时也确保只创建出超过期望数量的一定数量的pod,默认会确保最多比期望的pod数量多一个的pod是up的(最多一个surge)
- 未来的k8s版本中,将从1-1变成25%-25%
- kubectl describe deploy
- 为pod和rs提供了一个声明式定义(declarative)方法,用来替代以前的ReplicationController来方便的管理应用,典型的应用场景包括:
- 定义deploy来创建pod和rs
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续deploy
(命令式编程:侧重于如何实现程序,需要把程序的实现过程按照逻辑结果一步步写下来
声明式编程:侧重于定义想要什么,然后告诉计算机/引擎,让其帮你实现)
声明式(deploy):apply(最优) create也可
命令式(rs):create(最优),apply也可
Horizontal Pod Autoscaling
HPA仅适用于deploy和rs,自动平滑扩缩容
在v1版本中仅支持根据pod的cpu利用率扩缩容
在vlalpha版本中,支持根据内存和用户自定义的metric扩缩容
DaemonSet
ds确保全部(或一些,比如打了污点的node)Node上运行1个Pod的副本,有且只有1个
有Node加入集群时,也会为其新增1个Pod;当有Node从集群移除时,Pod也会被回收
删除ds将会删除它创建的全部Pod
- ds的一些典型用法:
- 运行集群存储daemon,例如在每个Node上运行glusterd、ceph
- 在每个Node上运行日志收集daemon,例如fluentd、logstash
- 在每个Node上运行监控daemon,例如prometheus node exporter、collectd、datadog代理、new relic代理、或ganglia gmond
StatefulSet
sts为了解决有状态服务的问题(对应deploy和rs是为无状态服务而设计)
- 应用场景:
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有ClusterIP的Service)实现有序部署,有序扩展,即Pod是有顺序的,在部署或扩展的时候要依据定义的顺序依次进行(即从0 - n-1,在下一个Pod运行前所有Pod必须是Running和Ready状态),基于init containers实现
- 有序收缩,有序删除(即从n-1 - 0)实现很难,目前mysql还不能稳定的在k8s中运行
Job
Job负责处理批任务,即仅执行一次的任务,保证批处理任务的1个或多个Pod成功结束
- 特殊说明
- spec.template格式同pod
- restartPolicy仅支持Never或OnFailure
- 单个Pod时,默认Pod成功运行后Job即结束
- .spec.completions标志job结束需要成功运行的pod个数,默认1
- .spec.parallelism标志并行运行的pod个数,默认1
- spec.activeDeadlineSeconds标志失败Pod的重试最大时间,超过这个时间不会继续重试
CronJob
管理基于时间的Job,即* * * * *
在给定时间点只运行一次
周期性地在给定时间点运行
使用前提条件:当前使用的k8s集群版本≥1.8(对cronjob)。对于先前版本的集群(版本<1.8),启动apiserver时,通过传递选项(–runtime-config=batch/v2alpha1=true)可以开启batch/v2alpha1API
典型的用法如下:
- 在给定的时间点调度job运行
- 创建周期性运行的job,例:数据库备份、发送邮件
## 特殊说明
spec.template格式同Pod
restartPolicy仅支持Never或OnFailure
单个Pod时,默认Pod成功运行后Job即结束 .spec.completions标志Jov结束需要成功运行的Pod个数,默认为1
.spec.parallelism标志并运行的Pod个数,默认为1
spec.activeDeadlineSeconds标志失败Pod的重试最大时间,超过这个时间不会继续重试
.spec.schedule:调度,必需字段,指定任务运行周期,格式同Cron
.spec.jobTemplate:job模板,必需字段,指定需要运行的任务,格式同Job
.spec.startingDeadlineSeconds:启动job的期限(s级别),可选字段。如果因为任何原因错过被调度的时间,那错过执行时间的job被认为是失败的,如果没有指定,则没有期限
.spec.concurrencyPolicy:并发策略,可选字段,指定了如何处理被CronJob创建的job的并发执行。只允许指定三种策略之一
Allow(默认):允许并发运行job
Forbid:禁止并发运行,如果前一个还未完成,直接跳过下一个
Replace:取消当前正运行的job,用新的替换
# 当前策略只能应用于同一个CronJob创建的Job,如果存在多个CronJob,他们创建的Job之间总是允许并发运行
.spec.suspend:挂起,可选字段。默认false,设置为true,后续所有执行都会被挂起,对已经开始执行的job不起作用
.spec.successfulJobsHistoryLimit和.spec.faildJobsHistoryLimit:历史限制,可选字段,指定了可以保留多少完成和失败的job。默认分别3(成功保留3个副本) 1(失败保留1个副本),设置限制的值为0,相关类型的job完成后将不会被保留cronjob是否成功不好判断,只负责定期创建job,job的成功失败不会链接到cronjob
网络
Service
服务发现,客户端想访问一组Pod,通过标签搜寻Pod
例:简单的一个svc(1个nginx、3个php Pod)3个pod的信息会记录到svc的负载队列,nginx配置信息只需指定到php的svc;pod如有更新会同步到svc里;nginx反向代理svc,svc会自动更新,不需要在nginx里做任何修改(不管pod扩缩容、更新,都不会对nginx或上一层服务造成影响)
概念:
k8s service 定义了这样一种抽象:1个pod的逻辑分组,一种可以访问他们的策略 – 通常称为微服务,每一个svc都是服务。这一组pod能够被service访问到,通常是通过label selector
svc能提供负载均衡的能力,但是在使用上有以下限制
默认只提供4层负载均衡能力,没有7层功能,也就是不能通过主机名、域名方案去负载均衡;有时需要更多的匹配规则来转发请求,这点上4层负载均衡不支持;可通过添加Ingress方案添加7层负载均衡的能力
svc类型
在k8s中有以下四种类型:
- ClusterIP(默认):自动分配一个仅Cluster内部可以访问的虚拟IP
主要在每个node节点使用iptables,将发向ClusterIP对应端口的数据,转发到kube-proxy中。kube-proxy自己内部实现有负载均衡的方法,并可以查询到service下对应pod的地址和端口,进而把数据转发给对应的pod的地址和端口
为了实现上图的功能,主要需要一下几个组件协同工作:
__apiserver__:用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
__kube-proxy__:kubernetes的每个节点中都有一个叫作kube-proxy的进程,这个进程负责感知service、pod的变化,并将变化的信息写入本地的iptables规则中
__iptables__:使用NAT等技术将virtualIP的流量转至endpoint中## Headless Service(无头服务,特殊的ClusterIP)
# 有时不需要或不想要负载均衡、以及单独的service ip(虚拟ip),可通过指定clusterip(spec.clusterIP)的值为None来创建Headless Service。这类service不会分配clusterip,kube-proxy不会处理他们,而且平台也不会为他们进行负载均衡和路由
apiVersion: v1
kind: Service
metadata:
name: myapp-headless
namespace: default
spec:
selector:
app: myqpp
clusterIP: "None"
ports:
- port: 80
targetPort: 80
(dig命令需yum -y install bind-utils)
dig -t A myapp-headless.default.svc.cluster.local. @10.96.0.10
无头服务中,虽然没有svc,但可通过域名访问至不同Pod - NodePort:在ClusterIP基础上位svc在每台机器上绑定一个端口,这样就可以通过
: 来访问该服务
在node上开了一个端口,将向该端口的流量导入到kube-proxy,然后由kube-proxy进一步给到对应的pod# 查询流程
netstat -anpt | grep :30715
iptables -t nat -nvL
ipvsadm -Ln - LoadBalancer:在NodePort基础上,借助cloud provider(云供应商,要收费)创建一个外部负载均衡器,并将请求转发到
:
和NodePort是同一种方式。区别在于LB比NodePort多了一步,就是可以调用cloud provider去创建LB来向节点导流,LB需花钱 - ExternalName:把集群外部的服务引入到集群内部来,在集群内部直接使用。没有任何类型代理被创建,这只有k8s1.7或更高版本的kube-dns才支持
这种类型的service通过返回CNAME和它的值,可以讲服务映射到externalName字段的内容(例:hub.jade.com)。ExternalName service是service的特例,它没有selector,也没有定义任何的端口和Endpoint。相反的,对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务当查询主机my-service.default.svc.cluster.local(SVC_NAME.NAMESPACE.svc.cluster.local)时,集群的DNS服务将返回一个值my.database.example.com的CNAME记录。访问这个服务的工作方式和其他的相同,唯一不同的是重定向发生在DNS层,且不会进行代理或转发kind: Servcie
apiVersion: v1
metadata:
name: my-service-1
namespace: default
spec:
type: ExternalName
externalName: hub.jade.com
dig -t A my-service-1.default.svc.cluster.local. @10.244.0.7
相当于DNS的别名操作,将外部服务引入集群内部
VIP和SVC代理
k8s集群中,每个Node运行一个kube-proxy进程。kube-proxy负责为svc实现了一种VIP(虚拟IP)的形式,而不是ExternalName的形式。
在k8s1.0版本,代理完全在userspace
在k8s1.1版本,新增iptables代理,但并不是默认的运行模式
从k8s1.2起,默认iptables代理
在k8s1.8.0-beata.0中,添加ipvs代理
在k8s1.14版本,开始默认使用ipvs代理
(性能递增)在k8s1.0版本,svc是4层(TCP/UDP over IP)概念
在k8s1.1版本,新增Ingress API(beta版),用来表示7层(HTTP)服务
?为什么不使用 round-robin DNS进行负载均衡
dns会在很多客户端里进行缓存,很多服务访问dns进行域名解析,得到地址后,很多服务都不会清空缓存,意味着一旦有地址信息,不管访问几次还是这个地址信息
代理模式分类
- userspace代理模式
- iptables代理模式
- ipvs代理模式
kube-proxy会监视k8s svc对象和endpoints,调用netlink接口以相应地创建ipvs规则并定期与k8s svc对象和endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod
与iptables类似,ipvs于netfilter的hook功能,但是用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs为负载均衡算法提供了更多选项,例如:
- rr:轮询调度
- lc:最小连接数
- dh:目标哈希
- sh:源哈希
- sed:最短期望延迟
- nq:不排队调度
ipvs模式假定在运行kube-proxy之前的节点上都已经安装了ipvs内核模块,当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了ipvs模块,如果未安装,则kube-proxy将回退到iptables代理模式
svc所需组件
Ingress
其实对于nginx来说,暴露方式依然是NodePort
nginx内部协程沟通方案
网络通讯模式
定义
k8s的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中(pod间可通过ip直接访问)
这在GCE(Google Compute Engine)里是现成的网络模型,k8s假定这个网络已存在
而在私有云里搭建k8s,就不能假定这个网络已存在,需要自己实现网络假设,将不同节点上的docker容器间的互相访问先打通,然后运行k8s
同一个Pod内的多个容器间:lo
各Pod之间的通讯:Overlay Network
Pod与Service之间的通讯:各节点的iptables规则
Flannel
- 定义
最常用的在k8s中解决网络扁平化的方案,符合cni接口
是CoreOS针对k8s设计的一个网络规划服务,简单说是 让集群中不同节点主机创建的docker容器都具有全集群唯一的虚拟IP地址。而且它还能在这些IP地址之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内 - ETCD之Flannel提供说明:
存储管理Flannel可分配的IP地址段资源 监控ETCD中每个Pod的实际地址,并在内存中建立维护Pod节点路由表
通讯方式
- 同一个Pod内部通讯:
同一个Pod共享同一个网络命名空间,共享同一个Linux协议栈(pause基础协议栈),直接使用lo(localhost)通讯 - Pod1至Pod2
不在同一台主机
Pod的地址是与docker0在同一个网段的,单docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过这个关联让Pod可以互相访问
在同一台机器
由docker0网桥直接转发请求至Pod2,不需要经过Flannel - Pod至Service的网络
目前基于性能考虑,全部为iptables维护和转发最新版是lvs进行转发维护 - Pod到外网
Pod向外网发送请求,查找路由表,转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执行Masquerade(snat),把源IP更改为宿主网卡的IP,然后向外网服务器发送请求 - 外网访问Pod
Service借助service的nodeport方式
Service网络(虚拟内部网络)
Pod网络(虚拟内部网络)
节点网络(真实物理网络)
存储
configmap(配置文件注册中心)
描述信息
ConfigMap功能在k8s1.2版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息
ConfigMap API给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制等对象
创建
- 使用目录创建
kubectl create configmap game-config –from-file=../configmap/kubectl
–from-file指定在目录下的所有文件都会被用在ConfigMap里面创建一个键值对,键:文件名;值:文件内容 - 使用文件创建
只要指定为一个文件就可以从单个文件中创建ConfigMap
kubectl create configmap game-config-2 –from-file=../configmap/kubectl/game.properties
–from-file这个参数可以使用多次,可以使用两次分别指定上个实例中两个配置文件,效果和指定整个目录是一样的 - 使用字面值创建
使用文字值创建,利用–from-literal参数传递配置信息,该参数可以使用多次,格式如下
kubectl create cm special-config –from-literal=special.how=very –from-literal=special.type=charm
Pod中使用ConfigMap
- 使用cm替代环境变量(通过cm把环境变量注入到pod内部)
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.type: charm
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: hub.jade.com/library/myapp:v1
command: ["/bin/sh", "-c", "env"]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.type
envFrom:
- configMapRef:
name: env-config
restartPolicy: Never
# kubectl log dapi-test-pod 会看到环境变量 - 用ConfigMap设置命令行参数
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.type: charm
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: hub.jade.com/library/myapp:v1
command: ["/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)"]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.type
restartPolicy: Never - 通过数据卷插件使用ConfigMap在数据卷里面使用这个cm,有不同的选项。
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
special.type: charm
最基本的就是将文件填入数据卷,在这个文件中,键:文件名;值:文件内容apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: hub.jade.com/library/myapp:v1
command: ["/bin/sh", "-c", "cat /etc/config/special.how"]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: special-config
restart Policy: Never
# kubectl exec <pod_name> -it -- /bin/sh
# cd /etc/config
# ls 可以看到special-config下的special.how和special.type - ConfigMap的热更新修改ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: log-config
namespace: default
data:
log_level: INFO
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: my-nginx
spec:
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
containers:
- name: my-nginx
image: hub.jade.com/library/myapp:v1
ports:
- containerPort: 80
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: log-config
# kubectl exec `kubectl get pods -l run=my-nginx -o=name | cut -d "/" -f2` -it -- cat /etc/config/log_level
INFOkubectl edit configmap log-config
- 修改log_level的值为DEBUG等待大概10s,再次查看环境变量的值
kubectl exec `kubectl get pods -l run=my-nginx -o=name | cut -d "/" -f2` -it -- cat /tmp/log_level
DEBUG特别注意:cm如果以env的方式挂载至容器,修改cm并不会实现热更新
cm更新后滚动更新Pod
更新cm目前并不会触发相关pod的滚动更新,可以通过修改pod annotations的方式强制触发滚动更新这个例子里我们在.spec.template.metadata.annotations中添加version/config,每次通过修改version/config来触发滚动更新kubectl patch deploy my-nginx --patch '{"spec": {"template": {"metadata": {"annotations": {"version/config": "20230301"}}}}}'
!!!更新ConfigMap后:
- 使用该cm挂载的env不会同步更新
- 使用该cm挂载的volume中的数据需要一段时间(实测大概10s)才能同步更新
Secret
描述信息
Secret解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者Pod Spec中。Secret可以以Volume或环境变量的方式使用
分类
- Service Account(不常用)
用来访问k8s API,由k8s自动创建,并且会自动挂载到Pod的/run/secrets/kubernetes.io/serviceaccount目录中# $ kubectl run nginx --image nginx
# deployment "nginx" created
# $ kubectl get pods
$ kubectl exec kube-proxy-fb85x -n kube-system -it -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt
namespace
token - Opaque
base64编码格式的Secret,用来存储密码、密钥等- 创建说明
Opaque类型的数据是一个map类型,要求value是base64编码格式:secrets.yml$ echo -n "admin" | base64
YWRtaW4=
$ echo -n "1f2d1e2e67df" | base64
MNYyDFJKS1jidfjapiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
password: MNYyDFJKS1jidfj
username: YWRtaW4= - 使用方式
## 1.将Secret挂载到Volume中
apiVersion: v1
kind: Pod
metadata:
labels:
name: secret-test
name: secret-test
spec:
volumes:
- name: secrets
secret:
secretName: mysecret
containers:
- image: hub.jade.com/library/myapp:v1
name: db
volumeMounts:
- name: secrets
mountPath: "/etc/secrets"
readOnly: true
# kubectl exec secret-test -it -- cat /etc/secrets/username
admin
## 2.将Secret导出到环境变量中
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: pod-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: pod-deployment
spec:
containers:
- name: pod-1
image: hub.jade.com/library/myapp:v1
ports:
- containerPort: 80
env:
- name: TEST_USER
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: TEST_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key:password
# kubectl exec pod-deployment-782347234-983284 -it -- echo $TEST_USER $TEST_PASSWORD
admin
1f2d1e2e67df
- 创建说明
- kubernetes.io/dockerconfigjson
用来存储私有docker registry的认证信息# 使用kubectl创建docker registry认证的secret
# $ kubectl create secret docker-registry myregistrykey --docker-server=DOCKER_REGISTRY_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
$ kubectl create secret docker-registry myregistrykey --docker-server=hub.jade.com --docker-username=admin --docker-password=Harbor12345 --docker-email=281108530@qq.com
secret "myregistrykey" created.
# 在创建Pod的时候,通过imagePullSecrets来引用刚创建的myregistrykey
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: hub.jade.com/test/myapp:v2 # 私有仓库的镜像
imagePullSecrets:
- name: myregistrykey # 使用secret docker-registry myregistrykey进行认证如果不指定imagePullSecrets,拉取私有仓库镜像时会报错
Volume
概念
容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。
首先,当容器崩溃时,kubelet会重启它,但是容器中的文件将丢失(容器以镜像初始状态重新启动);
其次,在Pod中同时运行多个容器时,这些容器之间通常需要共享文件,k8s中的Volume抽象可以很好的解决这些问题
背景
k8s中的卷有明确的寿命(与封装它的Pod相同),所以卷的生命比Pod中所有容器都长,当这个容器重启时,数据仍然得以保存
当然,当Pod不再存在时,卷也不复存在
k8s支持多种类型的卷,Pod可以同时使用任意数量的卷
pod重启后,volume数据还在
卷的类型
k8s支持以下类型的卷
- awsElsticVlockStor、azureDisk、azureFile、cephfs、csi、downwardAPI、emptyDir
- fc、flocker、gcePersistentDisk、gitRepo、glusterfs、hostPath、iscsi、local、nfs
- persistentVolumeClaim、projected、potworxVolume、quobyte、rbd、scaleIO、secret
- storagesos、vsphereVolume
emptyDir
当Pod被分配给节点时,首先创建emptyDir卷,并且只要该Pod在该节点上运行,该卷就会存在
正如卷名,它最初是空的
Pod中的容器可以读取和写入emptyDir卷中的相同文件,尽管该卷可以挂载到每个容器中的相同或不同路径上
当出于任何原因从节点中删除Pod时,emptyDir中的数据被永久删除容器崩溃不会从节点中移除Pod,因此emptyDir卷中的数据在容器崩溃时是安全的
【emptyDir用法】:
- 暂存空间,例如用于基于磁盘的合并排序
- 用作长时间计算崩溃恢复时的检查点
- Web服务器容器提供数据时,保存内容管理器容器提取的文件
apiVersion: v1
kind: Pod
metadata:
name: test-pod-emptyDir
spec:
containers:
- name: test-container1
image: hub.jade.com/test/myapp:v2
volumeMounts:
- mountPath: /cache
name: cache-volume
containers:
- name: test-container2
image: busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh", "-c", "sleep 6000s"]
volumeMounts:
- mountPath: /test
name: cache-volume
volumes:
- name: cache-volue
emptyDir: {}
# kubectl exec test-pod-emptyDir -it -- ls /cache
空的
# kubectl exec test-pod-emptyDir -c test-container1 -it -- cd /cache date>test.txt
# kubectl exec test-pod-emptyDir -c test-container2 -it -- cd /test date>>test.txt
两个目录下都有test.txt且内容相同
hostPath
hostPath卷将主机节点的文件系统中的文件或目录挂载到集群中
【hostPath用途】:
- 运行需要访问Docker内部的容器;使用/var/lib/docker的hostPath
- 在容器中运行cAdvisor;使用/dev/cgroups的hostPath
除了所需的path属性之外,用户还可以为hostPath卷指定type
值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,意味着在挂载hostPath卷之前不会执行任何检查 | |
DirectoryOrCreate | 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为0755,与kubelet具有相同的组和所有权 |
Directory | 给定的路径下必须存在目录 |
FileOrCreate | 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为0644,与kubelet具有相同的组和所有权 |
File | 给定的路径下必须存在文件 |
Socket | 给定的路径下必须存在UNIX套接字 |
CharDevice | 给定的路径下必须存在字符设备 |
BlockDevice | 给定的路径下必须存在块设备 |
使用hostPath卷类型时请注意:
- 由于每个节点上的文件不同,具有相同配置(例如从podTemplate创建的)的pod在不同节点上的行为可能会有所不同
- 当k8s按照计划添加资源感知调度时,将无法考虑hostPath使用的资源
- 在底层主机上创建的文件或目录只能由root写入。需要在特权容器中以root身份运行进程,或修改主机上的文件权限以便写入hostPath卷
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- name: test-container
image: hub.jade.com/test/myapp:v2
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
# directory location on host
path: /data
# this field is optional
type: Directory
# 进容器写入文件
kubectl exec test-pd -it -- /bin/sh
cd /test-pd/
date > test.txt
# 到主机节点查看
cat /data/test.txt
Persistent Volume(PV)持久卷
是由管理员设置的存储,它是集群的一部分
就像节点是集群中的资源一样,PV也是集群中的资源
PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期
此API对象包含存储实现的细节,即NFS、iSCSI或特定于云供应商的存储系统
静态pv
集群管理员创建一些pv,他们带有可供集群用户使用的实际存储的细节,他们存在于k8s api中,可用于消费
动态
暂时不友好,实现方案较难 价格昂贵
当管理员创建的静态pv都不匹配用户的pvc时,集群可能会尝试动态地为pvc创建卷,此配置基于StorageClasses:pvc必须请求【存储类】,并且管理员必须创建并配置该类才能进行动态创建,声明该类为可以有效地禁用其动态配置
要启用基于存储级别的动态存储配置,集群管理员需要启用api server上的DefaultStorageClass【准入控制器】。例如,通过确保DefaultStorageClass位于apiServer组件的–admission-control标志,使用逗号分隔的有序值列表中,可以完成此操作
类型
pv类型以插件形式实现,k8s目前支持以下插件类型
- GCEPersistentDisk AWSElasticBlockStore AzureFile FC(Fibre Channel)
- FlexVolume Flocker NFS iSCSI RBD(Ceph Block Device) CephFS
- Cinder(OpenStack block storage) Glusterfs VsphereVolume Quobyte Volumes
- HostPath VMware Photon Portwirx Volumes ScaleIO Volumes StorageOS
pv演示代码
# nfs机制 |
pv访问模式
PersistentVolume可以以资源提供者支持的任何方式挂载到主机上,如下表所示,供应商具有不通的功能,每个pv的访问模式都将被设置为该卷支持的特定模式。例如,nfs可以支持多个读/写客户端,但特定的NFS PV可能以只读方式导出到服务器上,每个pv都有一套自己的用来描述特定功能的访问模式
- ReadWriteOnce–该卷可被单个节点以读写模式挂载
- ReadOnlyMany–该卷可被多个节点以只读模式挂载
- ReadWriteMany–该卷可被多个节点以读写模式挂载
在命令行中,accessModes缩写:RWO ROX RWX1个卷1次只能使用1种访问模式挂载,即使它支持很多访问模式,例如GCEPersistentDisk可以由单个节点作为RWO模式挂载,或由多个节点以ROX模式挂载,但不能同时挂载
回收策略
- Retain(保留)– 手动回收(pv不再继续使用时也不允许被别人使用,进入保留状态,等待管理员手动释放)
- Recycle(回收)– 基本擦除(rm -rf /thevolume/*)已废弃 - Delete(删除)– 关联的存储资产(例如AWS EVS、GCE PD、Azure Disk和OpenStack Cinder卷)将被删除
当前,只有NFS和HostPath支持回收策略。AWS EBS、GCE PD、Azure Disk和Cinder卷支持删除策略
状态
卷可以处于一下某种状态:
- Available(可用)– 一块空闲资源还没有被任何声明绑定
- Bound(已绑定)– 卷已经被声明绑定
- Released(已释放)– 声明被删除,但是资源还未被集群重新声明
- Failed(失败)– 该卷的自动回收失败
命令行会显示绑定到pv的pvc的名称
PersistentVolumeClaim(PVC)持久卷声明
是用户存储的请求,与Pod相似
Pod消耗节点资源,pvc消耗pv资源
Pod可以请求特定级别的资源(CPU、Memory)
声明可以请求特定的大小和访问模式(例如可以以读/写一次或只读多次模式挂载)
绑定
master中的控制环路监视新的pvc,寻找匹配的pv(如果可能),并将他们绑定在一起,如果为新的pvc动态调配pv,则该环路将始终将该pv绑定到pvc。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量,一旦pv和pvc绑定后,PersistentVolumeClaim绑定是排他性的,不管他们是如何绑定的,pvc跟pv绑定是一对一的映射
pvc的保护
pvc保护的目的是确保由pod正在使用的pvc不会从系统中移除,因为如果被移除的话,可能会导致数据丢失
注意:当pod状态为Pending且pod已经分配给节点 或 pod为Running状态时,pvc处于活动状态
当启用pvc保护alpha功能时,如果用户删除了一个pod正在使用的pvc,则该pvc不会被立即删除,pvc的删除将被推迟,直到ovc不再被任何pod使用
持久化演示说明 - NFS
- 安装NFS服务器
yum install -y nfs-common nfs-utils recbind
mkdir /nfsdata
chmod 666 /nfsdata
chown nfsnobody /nfsdata
cat /etc/exports
/nfsdata *(rw,no_root_squash,no_all_squash,sync)
/nfsdata1 *(rw,no_root_squash,no_all_squash,sync)
/nfsdata2 *(rw,no_root_squash,no_all_squash,sync)
/nfsdata3 *(rw,no_root_squash,no_all_squash,sync)
systemctl start rpcbind
systemctl start nfs
## 测试nfs是否可用
mkdir /test
# 查看共享目录
showmount -e 192.168.66.100
# 挂载
mount -t nfs 192.168.66.100:/nfs /test/
# 解除挂载
umount /test/ - 部署PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfspv1
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /nfs
server: 192.168.66.100 - 创建服务并使用pvc
# 想创建sts,必须先创建无头svc,不需要对应的ip及端口
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # 无头服务
selector:
app: nginx
apiVersion: apps/v1
kind: StatefuleSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: hub.jade.com/library/myapp:v2
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi # ≥此大小即可,优先选符合条件最小的
关于sts
- 匹配Pod name(网络标识)的模式为:${sts名称}-${序号},比如上面的示例web-0、web-1、web-2
- sts为每个pod副本创建了一个dns域名,这个域名的格式为${podName}.${headless server name},也就意味着服务间是通过pod域名来通信而非Pod IP,因为当Pod所在node发生故障时,pod会被飘移到其他node上,pod IP会发生变化,但是Pod域名不会有变化
- sts使用Headless服务来控制Pod的域名,这个域名的FQDN为${servicename}.${ns}.svc.cluster.local,其中,cluster.local指的是集群的域名
- 根据volumeClaimTemplates,为每个Pod创建一个pvc,pvc的命名规则匹配模式:${volumeClaimTemplates.name}-${pod_name},比如上面的volumeMounts.name=www,Podname=web-[0-2],因此创建出来的PVC是www-web-0、www-web-1、www-web-2
- 删除pod不会删除其pvc,手动删除pvc将自动释放pv
sts的启停顺序
- 有序部署:部署sts时,如有多个pod副本,会被按顺序地创建(从0到N-1)并且,在下一个pod运行之前所有pod必须是running和ready状态
- 有序删除:当pod被删除时,它们被终止的顺序是从n-1到0
- 有序扩展:当对pod执行扩展时,与部署一样,前边pod必须是running和ready
sts使用场景
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于pvc来实现
- 稳定的网络标识符,即pod重新调度后其PodName和HostName不变
- 有序部署,有序扩展,基于init containers来实现
- 有序收缩
说些什么吧!