云原生
为了让应用程序(项目,服务软件)都运行在云上的解决方案,这样方案叫做云原生,有以下特点:
- 容器化:所有的服务都必须部署在容器中。
- 微服务:web 服务架构是微服务架构
- CI/CD:可持续交互和可持续部署
- DevOps:开发和运维密不可分
k8s 的架构以及组件
master 节点
api server:
即kubernetes API 所有资源增删改查的唯一入口,比如:统一进行身份认证、授权、API验证、和集群状态的持久化........等功能
etcd :
存储所有集群的元数据、和状态信息的数据库,kube-apiserver 通过读写 etcd 来管理集群的状态信息,其他组件通过 kube-apiserver 访问 etcd 中的数据。etcd 通过使用 Raft 协议,来实现分布式一致性,确保数据在多个节点之间的一致性。不仅仅用于提供键值数据存储,而且还为其提供了监听(watch)机制,用于监听和推送变更
scheduler:
根据资源需求、策略和约束条件,选择合适的节点运行Pod
•预选策略(predicate)
•优选策略(priorities)
API Server 接收到请求创建一批 Pod ,API Server 会让 Controller-manager 按照所预设的模板去创建 Pod,Controller-manager 会通过 API Server 去找 Scheduler 为新创建的 Pod 选择最适合的 Node 节点。比如运行这个 Pod 需要 2C4G 的资源,Scheduler 会通过预选策略过滤掉不满足策略的 Node 节点。Node 节点中还剩多少资源是通过汇报给 API Server 存储在 etcd 里,API Server 会调用一个方法找到 etcd 里所有 Node 节点的剩余资源,再对比 Pod 所需要的资源,如果某个 Node 节点的资源不足或者不满足 预选策略的条件则无法通过预选。预选阶段筛选出的节点,在优选阶段会根据优先策略为通过预选的 Node 节点进行打分排名, 选择得分最高的 Node。例如,资源越富裕、负载越小的 Node 可能具有越高的排名。
Kube-controller-manager::
它聚合并管理了一系列内置的 Controllers,这些 Controllers 负责监控和维护集群内各种资源对象的状态,确保集群的实际状态与用户的期望状态一致 由控制器完成的主要功能主要包括生命周期功能和API业务逻辑,具体如下:
- 生命周期功能:包括Namespace创建和生命周期、Event垃圾回收、Pod终止相关的垃圾回收、级联垃圾回收及Node垃圾回收等。
- API业务逻辑:例如,由ReplicaSet执行的Pod扩展等。
- Node Controller(节点控制器):负责在节点出现故障时发现和响应。
- Replication Controller(副本控制器):负责保证集群中一个 RC(资源对象 Replication
- Controller)所关联的 Pod 副本数始终保持预设值。可以理解成确保集群中有且仅有 N 个 Pod 实例,N 是 RC 中定义的 Pod 副本数量。
- Endpoints Controller(端点控制器):填充端点对象(即连接 Services 和 Pods),负责监听 Service 和对应的 Pod 副本的变化。 可以理解端点是一个服务暴露出来的访问点,如果需要访问一个服务,则必须知道它的 endpoint。
- Service Account & Token Controllers(服务帐户和令牌控制器):为新的命名空间创建默认帐户和 API 访问令牌。
- ResourceQuota Controller(资源配额控制器):确保指定的资源对象在任何时候都不会超量占用系统物理资源。
- Namespace Controller(命名空间控制器):管理 namespace 的生命周期。
- Service Controller(服务控制器):属于 K8S 集群与外部的云平台之间的一个接口控制器。
node 节点(工作节点)
Container Runtime
每个Node都需要提供一个容器运行时(Container Runtime)环境,它负责下载镜像并运行容器。目前K8S支持的容器运行环境至少包括Docker、RKT、cri-o、Fraki等。
kube-proxy:
网络代理服务,负责将访问的service的TCP/UDP数据流转发到后端的容器,如果有多个副本,kube-proxy会实现负载均衡,有2种方式:ipvs或者Iptables
kubelet
scheduler 把请求交给api ,然后 api sever 再把信息指令数据存储在 etcd 里,于是 kuberlet 会扫描 etcd 并获取指令请求,kubelet会根据这些信息创建和运行容器,并向master报告运行状态
工作负载
pod
apiVersion | 含义 |
---|---|
alpha | 进入K8s功能的早期候选版本,可能包含Bug,最终不一定进入K8s |
beta | 已经过测试的版本,最终会进入K8s,但功能、对象定义可能会发生变更。 |
stable | 可安全使用的稳定版本 |
v1 | stable 版本之后的首个版本,包含了更多的核心对象 |
apps/v1 | 使用最广泛的版本,像Deployment、ReplicaSets都已进入该版本 |
资源类型与apiVersion对照表
Kind | apiVersion |
---|---|
ClusterRoleBinding | rbac.authorization.k8s.io/v1 |
ClusterRole | rbac.authorization.k8s.io/v1 |
ConfigMap | v1 |
CronJob | batch/v1beta1 |
DaemonSet | extensions/v1beta1 |
Node | v1 |
Namespace | v1 |
Secret | v1 |
PersistentVolume | v1 |
PersistentVolumeClaim | v1 |
Pod | v1 |
Deployment | v1、apps/v1、apps/v1beta1、apps/v1beta2 |
Service | v1 |
Ingress | extensions/v1beta1 |
ReplicaSet | apps/v1、apps/v1beta2 |
Job | batch/v1 |
StatefulSet | apps/v1、apps/v1beta1、apps/v1beta2 |
pod种类
●自主式Pod 这种Pod本身是不能自我修复的,当Pod被创建后(不论是由你直接创建还是被其他Controller),都会被Kuberentes调度到集群的Node上。直到Pod的进程终止、被删掉、因为缺少资源而被驱逐、或者Node故障之前这个Pod都会一直保持在那个Node上。Pod不会自愈。如果Pod运行的Node故障,或者是调度器本身故障,这个Pod就会被删除。同样的,如果Pod所在Node缺少资源或者Pod处于维护状态,Pod也会被驱逐。
●控制器管理的Pod Kubernetes使用更高级的称为Controller的抽象层,来管理Pod实例。Controller可以创建和管理多个Pod,提供副本管理、滚动升级和集群级别的自愈能力。例如,如果一个Node故障,Controller就能自动将该节点上的Pod调度到其他健康的Node上。虽然可以直接使用Pod,但是在Kubernetes中通常是使用Controller来管理Pod的。
pod中容器种类
基础容器 (pause) :初始化容器环境,开启pid=1的Init进程来管理其它容器的生命周期:提供网络和存储空间的共享环境基础
init****容器:是在基础容器之后,应用容器之前运行的容器,多个init容器是串行运行,Init容器必须在上一个init容器成功运行和退出后才能运行
应用容器 (main c) :运行业务的容器,在Init容器都成功运行和退出后运行的,多个应用容器是并行运行的
在一个pod 中,init容器和应用容器的名称都是唯一的
Pod生命周期状态
Pending:API Server已经创建该Pod,Pod 信息已经提交给了集群,但是还没有被调度器调度到合适的节点或者 Pod 里的镜像正在下载,或者是下载镜像慢,调度不成功
Running:Pod内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或正在重启状态。
Succeeded:Pod内所有容器均成功执行退出,且不会重启。
Failed:Pod内所有容器均已退出,但至少有一个容器退出为失败状态。
Unknown:apiserver无法取得 Pod 的状态,通常是master与与 Pod 所在主机通信失败导致的可能由于网络通信不畅导致。
terminate:pod无法删除,可能会出现terminate状态,需要强行删除pod
Error 状态
> 通常处于 Error 状态说明 Pod 启动过程中发生了错误。常见的原因包括:
- 依赖的 ConfigMap、Secret 或者 PV 等不存在
- 请求的资源超过了管理员设置的限制,比如超过了 LimitRange 等
- 违反集群的安全策略,比如违反了 PodSecurityPolicy 等
- 容器无权操作集群内的资源,比如开启 RBAC 后,需要为 ServiceAccount 配置角色绑定。
ImagePullPolicy
支持三种 ImagePullPolicy
- Always:不管本地镜像是否存在都会去仓库进行一次镜像拉取。校验如果镜像有变化则会覆盖本地镜像,否则不会覆盖。
- Never:只是用本地镜像,不会去仓库拉取镜像,如果本地镜像不存在则 Pod 运行失败。
- IfNotPresent:只有本地镜像不存在时,才会去仓库拉取镜像。ImagePullPolicy的默认值。
注意:
- 默认为
IfNotPresent
,但:latest
标签的镜像默认为Always
。 - 拉取镜像时 docker 会进行校验,如果镜像中的 MD5 码没有变,则不会拉取镜像数据。
- 生产环境中应该尽量避免使用
:latest
标签,而开发环境中可以借助:latest
标签自动拉取最新的镜像。
imagePullSecrets
拉取镜像的认证配置信息
imagePullSecrets:
- name: hearbor-secret
例如访问 harbor 时需要鉴权,创建secret:
kubectl create secret docker-registry -n kube-system harbor-registry --docker-username=XXXX --docker-password="YYYYYY" --docker-email=aaa@1test.com --docker-server=harbor.test.env2.com
restartPolicy
支持三种 RestartPolicy,可选项包括 Always
、OnFailure
、以及 Never
。
- Always:当容器失效时,由Kubelet自动重启该容器。RestartPolicy的默认值。
- OnFailure:当容器终止运行且退出码不为0时由Kubelet重启。
- Never:无论何种情况下,Kubelet都不会重启该容器。
dnsPolicy
通过设置 dnsPolicy 参数,设置 Pod 中容器访问 DNS 的策略。会直接影响 pod 内 /etc/resolv.conf
文件内容。
- ClusterFirst:优先基于 cluster domain (如
default.svc.cluster.local
) 后缀,通过 kube-dns 查询 (默认策略)。 /etc/resolv.conf 中的 dnsserveer 为集群中的 dns clusterip地址。当容器为 hostNetwork 时,会使用宿主机的 /etc/resolv.conf 挂载到 Pod 容器中。 - Default:优先从 Node 中配置的 DNS 查询。
- ClusterFirstWithHostNet: 在 host 网络时,也是用集群的 dnsserver 配置 /etc/reslov.conf 。
hostNetwork
使用主机的网络命名空间
通过设置 spec.hostNetwork
参数为 true,使用主机的网络命名空间,默认为 false。
使用宿主机的网络命名空间时,pod运行的端口会在宿主机上监听,即可以直接通过宿主机的 IP地址 + 容器运行端口的方式访问容器内的服务。
在使用 hostNetwork 时,默认情况下 /etc/resolv.conf 会挂载 宿主机的配置;如果这时也想使用 集群的 dnserver 时,可以将配置 dnsPolicy: ClusterFirstWithHostNet
。
hostIPC && hostPID
使用主机的 IPC 命名空间
通过设置 spec.hostIPC
参数为 true,使用主机的 IPC 命名空间,默认为 false。
使用主机的 PID 空间
通过设置 spec.hostPID
参数为 true,使用主机的 PID 命名空间,默认为 false。
将hostPID设置为true,pod内就可以看到宿主机上的所有进程pid,同时自己的进程也会有宿主机管理。
将hostIPC设置为true,pod中的进程就可以与宿主机上的其他所有进程进行通信。
hostname
设置 Pod 的 hostname
通过 spec.hostname
参数实现,如果未设置默认使用 metadata.name
参数的值作为 Pod 的 hostname。
metadata.name
有 kubernetes 自动生成。
HostAliases
自定义 hosts
默认情况下,容器的 /etc/hosts
是 kubelet 自动生成的,并且仅包含 localhost 和 podName 等。不建议在容器内直接修改 /etc/hosts
文件,因为在 Pod 启动或重启时会被覆盖。
LivenessProbe & ReadinessProbe
为了确保容器在部署后确实处在正常运行状态,Kubernetes 提供了两种探针(Probe)来探测容器的状态:
- LivenessProbe:探测应用是否处于健康状态,如果不健康则删除并重新创建容器。
- ReadinessProbe:探测应用是否启动完成并且处于正常服务状态,如果不正常则不会接收来自 Kubernetes Service 的流量,即将该Pod从Service的endpoint中移除。
Kubernetes 支持三种方式来执行探针:
- exec:在容器中执行一个命令,如果 命令退出码 返回
0
则表示探测成功,否则表示失败 - tcpSocket:对指定的容器 IP 及端口执行一个 TCP 检查,如果端口是开放的则表示探测成功,否则表示失败
- httpGet:对指定的容器 IP、端口及路径执行一个 HTTP Get 请求,如果返回的 状态码 在
[200,400)
之间则表示探测成功,否则表示失败
探针的附加属性参数有以下几个:
initialDelaySeconds: 容器启动多少秒后开始执行探测,默认是0s
periodseconds:探测的周期,每多少秒执行一次探测,默认10s,最小值为1次
failureThreshold: 探测失败后,允许再试几次, 默认3次,最小值为1次
successThreshold: 连续几次探测成功,该探针被认为是成功的,默认1次,最小值为1次
timeoutseconds :探测等待超时的时间,默认1s,最小值为1次
注意:定义存活探针时,一定要设置initialDelaySeconds属性,该属性为初始延时,如果不设置,默认容器启动时探针就开始探测了,这样可能会存在
应用程序还未启动就绪,就会导致探针检测失败,k8s就会根据pod重启策略杀掉容器然后再重新创建容器的莫名其妙的问题。
在生产环境中,一定要定义一个存活探针。
lifecycle
容器生命周期钩子
容器生命周期钩子(Container Lifecycle Hooks)监听容器生命周期的特定事件,并在事件发生时执行已注册的回调函数。支持两种钩子:
- postStart: 容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。如果失败,容器会被杀死,并根据 RestartPolicy 决定是否重启
- preStop:容器终止前执行,常用于资源清理。如果失败,容器同样也会被杀死
而钩子的回调函数支持两种方式:
- exec:在容器内执行命令,如果命令的退出状态码是
0
表示执行成功,否则表示失败 - httpGet:向指定 URL 发起 GET 请求,如果返回的 HTTP 状态码在
[200, 400)
之间表示请求成功,否则表示失败
nodeSelector
调度到指定的 Node 上
可以通过 nodeSelector、nodeAffinity、podAffinity 以及 Taints 和 tolerations 等来将 Pod 调度到需要的 Node 上。
也可以通过设置 nodeName 参数,将 Pod 调度到指定 node 节点上。
比如,使用 nodeSelector,首先给 Node 加上标签:
kubectl label nodes <your-node-name> disktype=ssd
pod调度策略
Predicate 有一系列的常见的算法可以使用:
●PodFitsResources:节点上剩余的资源是否大于 pod 请求的资源。
●PodFitsHost:如果 pod 指定了 NodeName,检查节点名称是否和 NodeName 匹配。
●PodFitsHostPorts:节点上已经使用的 port 是否和 pod 申请的 port 冲突。
●PodSelectorMatches:过滤掉和 pod 指定的 label 不匹配的节点。
●NoDiskConflict:已经 mount 的 volume 和 pod 指定的 volume 不冲突,除非它们都是只读。
如果在 predicate 过程中没有合适的节点,pod 会一直在 pending 状态,不断重试调度,直到有节点满足条件。 经过这个步骤,如果有多个节点满足条件,就继续 priorities 过程:按照优先级大小对节点排序。
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。有一系列的常见的优先级选项包括:
●LeastRequestedPriority:通过计算CPU和Memory的使用率来决定权重,使用率越低权重越高。也就是说,这个优先级指标倾向于资源使用比例更低的节点。
●BalancedResourceAllocation:节点上 CPU 和 Memory 使用率越接近,权重越高。这个一般和上面的一起使用,不单独使用。比如 node01 的 CPU 和 Memory 使用率 20:60,node02 的 CPU 和 Memory 使用率 50:50,虽然 node01 的总使用率比 node02 低,但 node02 的 CPU 和 Memory 使用率更接近,从而调度时会优选 node02。
●ImageLocalityPriority:倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高。
nodename(不经过scheduler) nodeselector(经scheduler匹配label)
affinity
kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector
的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。
Affinity主要分为三类:
nodeAffinity(node亲和性)
: 以node为目标,解决pod可以调度到哪些node的问题podAffinity(pod亲和性)
: 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题podAntiAffinity(pod反亲和性)
: 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题
**污点与容忍**
//污点(Taint)
节点亲和性,是Pod的一种属性(偏好或硬性要求),它使Pod被吸引到一类特定的节点。Taint 则相反,它使节点能够排斥一类特定的 Pod。
Taint 和 Toleration 相互配合,可以用来避免 Pod 被分配到不合适的节点上。每个节点上都可以应用一个或多个 taint ,这表示对于那些不能容忍这些 taint 的 Pod,是不会被该节点接受的。如果将 toleration 应用于 Pod 上,则表示这些 Pod 可以(但不一定)被调度到具有匹配 taint 的节点上。
使用 kubectl taint 命令可以给某个 Node 节点设置污点,Node 被设置上污点之后就和 Pod 之间存在了一种相斥的关系,可以让 Node 拒绝 Pod 的调度执行,甚至将 Node 已经存在的 Pod 驱逐出去。
污点的组成格式如下:
key=value:effect
每个污点有一个 key 和 value 作为污点的标签,其中 value 可以为空,effect 描述污点的作用。
当前 taint effect 支持如下三个选项:
●NoSchedule:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上
●PreferNoSchedule:表示 k8s 将尽量避免将 Pod 调度到具有该污点的 Node 上
●NoExecute:表示 k8s 将不会将 Pod 调度到具有该污点的 Node 上,同时会将 Node 上已经存在的 Pod 驱逐出去
kubectl taint node node-0001 key=value:NoSchedule # 设置污点标签
kubectl taint node node-0001 key- # 删除污点标签
kubectl describe nodes | grep -i taints # 查看污点标签
//容忍(Tolerations)
设置了污点的 Node 将根据 taint 的 effect:NoSchedule、PreferNoSchedule、NoExecute 和 Pod 之间产生互斥的关系,Pod 将在一定程度上不会被调度到 Node 上。但我们可以在 Pod 上设置容忍(Tolerations),意思是设置了容忍的 Pod 将可以容忍污点的存在,可以被调度到存在污点的 Node 上。
//其它注意事项 (1)当不指定 key 值时,表示容忍所有的污点 key tolerations:
- operator: "Exists"
(2)当不指定 effect 值时,表示容忍所有的污点作用 tolerations:
- key: "key" operator: "Exists"
(3)有多个 Master 存在时,防止资源浪费,可以如下设置 kubectl taint node Master-Name node-role.kubernetes.io/master=:PreferNoSchedule
//如果某个 Node 更新升级系统组件,为了防止业务长时间中断,可以先在该 Node 设置 NoExecute 污点,把该 Node 上的 Pod 都驱逐出去 kubectl taint node node01 check=mycheck:NoExecute
//此时如果别的 Node 资源不够用,可临时给 Master 设置 PreferNoSchedule 污点,让 Pod 可在 Master 上临时创建 kubectl taint node master node-role.kubernetes.io/master=:PreferNoSchedule
//待所有 Node 的更新操作都完成后,再去除污点 kubectl taint node node01 check=mycheck:NoExecute-
维护操作 //cordon 和 drain ##对节点执行维护操作: kubectl get nodes
//将 Node 标记为不可调度的状态,这样就不会让新创建的 Pod 在此 Node 上运行 kubectl cordon <NODE_NAME> #该node将会变为SchedulingDisabled状态
//kubectl drain 可以让 Node 节点开始释放所有 pod,并且不接收新的 pod 进程。drain 本意排水,意思是将出问题的 Node 下的 Pod 转移到其它 Node 下运行 kubectl drain <NODE_NAME> --ignore-daemonsets --delete-local-data --force
--ignore-daemonsets:无视 DaemonSet 管理下的 Pod。 --delete-local-data:如果有 mount local volume 的 pod,会强制杀掉该 pod。 --force:强制释放不是控制器管理的 Pod,例如 kube-proxy。
注:执行 drain 命令,会自动做了两件事情: (1)设定此 node 为不可调度状态(cordon) (2)evict(驱逐)了 Pod
//kubectl uncordon 将 Node 标记为可调度的状态 kubectl uncordon <NODE_NAME>
service
service的两大作用:
服务注册与发现 负载均衡
Service 作用于哪些 Pod 是通过标签选择器来定义的。
在 K8S 集群中,Service 可以看作一组提供相同服务的 Pod 的对外访问接口。客户端需要访问的服务就是 Service 对象。每个 Service 都有一个固定的虚拟 ip(这个 ip 也被称为 Cluster IP),自动并且动态地绑定后端的 Pod,所有的网络请求直接访问 Service 的虚拟 ip,Service 会自动向后端做转发。
service yaml文件
Service yaml文件详解
apiVersion: v1
kind: Service
matadata: #元数据
name: string #service的名称
namespace: string #命名空间
labels: #自定义标签属性列表
- name: string
annotations: #自定义注解属性列表
- name: string
spec: #详细描述
selector: [] #label selector配置,将选择具有label标签的Pod作为管理
#范围
type: string #service的类型,指定service的访问方式,默认为
#clusterIp
clusterIP: string #虚拟服务地址
sessionAffinity: string #是否支持session
ports: #service需要暴露的端口列表
- name: string #端口名称
protocol: string #端口协议,支持TCP和UDP,默认TCP
port: int #服务监听的端口号
targetPort: int #需要转发到后端Pod的端口号
nodePort: int #当type = NodePort时,指定映射到物理机的端口号
status: #当spce.type=LoadBalancer时,设置外部负载均衡器的地址
loadBalancer: #外部负载均衡器
ingress: #外部负载均衡器
ip: string #外部负载均衡器的Ip地址值
hostname: string #外部负载均衡器的主机名
service类型(四层 ip+端口)
cluster IP
这是K8S默认的服务类型 这种类型的service 只能在集群内访问
NodePort
将 service的端口映射到 Node 的一个端口上,然后通过 NodeIp+NodePort进行访问
会占用集群机器的很多端口,当集群服务变多的时候,这个缺点就越发明显
LoadBalancer
在 NodePort 的基础上 通过**向底层云平台申请创建一个负载均衡器**来向外暴露服务
外部环境发送到这个设备的请求,并将请求转发到 :NodePort
每个Service都需要一个LB,比较麻烦和浪费资源,并且需要 k8s之外的负载均衡设备支持
ExternalName
将服务通过 DNS CNAME 记录方式转发到指定的域名(通过spec.externlName 设定)
ClusterIP、NodePort、LoadBalancer每一个都是前者的加强版,也就意味着ClusterIP是最基础的
HeadLiness
如果一个service没有IP,那么就叫**Headless Service** **(无头服务)** ,如果想要访问 service,只能通过 Service 域名进行查询,可以直接解析到后端的PodIP,如果不是无头service那么解析的是ServiceIP
Label与Selector
Label
> Label是Kubernetes系列中另外一个核心概念。**是一组绑定到K8s资源对象上的key/value对**。同一个对象的labels属性的key必须唯一。label可以附加到各种资源对象上,如Node,Pod,Service,RC等。
> 通过给指定的资源对象捆绑一个或多个不用的label来实现多维度的资源分组管理功能,以便于灵活,方便地进行资源分配,调度,配置,部署等管理工作。
示例如下:
* 版本标签:"release" : "stable" , "release" : "canary"...
* 环境标签:"environment" : "dev" , "environment" : "production"
* 架构标签:"tier" : "frontend" , "tier" : "backend" , "tier" : "middleware"
* 分区标签:"partition" : "customerA" , "partition" : "customerB"...
* 质量管控标签:"track" : "daily" , "track" : "weekly"
Selector
> Label selector是Kubernetes核心的分组机制,通过label selector客户端/用户能够识别一组有共同特征或属性的资源对象。符合这个标签的 Pod 会作为这个 Service 的 backend。
apiVersion: v1
kind: Service
metadata:
name: hello
labels:
app: hello
spec:
ports:
- port: 80
targetPort: 80
selector:
app: hello
CoreDNS
k8s主要有两种service发现机制:环境变量和DNS。没有DNS服务的时候,k8s会采用环境变量的形式,但一旦有多个service,环境变量会变复杂,为解决该问题,我们使用DNS服务
在默认情况下,Kubernetes 将 CoreDNS 作为默认的 DNS 服务器,并将其 IP 地址添加到每个 Pod 的 /etc/resolv.conf
文件中。
例如,当从 Pod A 中解析 Pod B 名称时,Pod A 将查找其 /etc/resolv.conf
文件中定义的 DNS 服务器 IP 地址(即 CoreDNS 的 IP 地址),并向其发送 DNS 查询请求。CoreDNS 将查询 Kubernetes API Server,获取有关 Pod B 的信息,并将其返回给 Pod A。
需要注意的是,CoreDNS 的 IP 地址必须在每个节点上可访问,以便让所有的 Pod 都能够进行 DNS 查询。默认情况下,Kubernetes 会在每个节点上创建一个名为 kube-dns
的服务,该服务将 CoreDNS 的 IP 地址暴露给其他 Pod。
- CoreDNS会检查请求中的域名是否以.svc.cluster.local结尾。如果是,则进入Service发现阶段。如果请求不以.svc.cluster.local结尾,则进入正常DNS解析阶段。
- Service发现阶段:CoreDNS向Kubernetes API服务器发送请求,获取对应Service的Cluster IP地址,并返回给客户端。
- 正常DNS解析阶段:如果不是以.svc.cluster.local结尾的域名,CoreDNS会使用插件机制来查询指定的数据源(例如:文件、DNS记录、HTTP API等)获取结果,并返回给客户端。
kube-proxy
kube-proxy工作模式
kube-proxy
会基于监听的机制发现这种 Service 的变动,然后 它会将最新的Service信息转换成对应的访问规则
可通过以下三种流量调度模式: userspace(废弃)、iptables(濒临废弃)、ipvs(推荐,性能最好)来实现网络的转发
userspace
当有请求发往Cluster IP
的时候,会被 Iptables 规则重定向到 kube-proxy 监听的端口上,kube-proxy 会根据 LB 算法选择一个 Pod 提供服务并建立起连接。
iptables
在 iptables 模式下,kube-proxy 会为 Service 后端的每个 pod 都创建对应的 iptable 规则,直接将发往 Cluster IP
的请求重定向到一个 pod IP 上。该模式下 kube-proxy 不承担四层负载均衡器的角色,只负责创建 iptables 的规则。该模式的优点便是较 userspace 模式来说效率更高,但是不能提供灵活的 LB 策略。当后端Pod不可用的时候也无法进行重试
lvs
负载均衡,并使用高效的数据结构Hash表,允许几乎无限的规模扩张。只需要将相关IP地址加入ipset集合中即可,这样只需要设置少量的iptables规则即可实现目标。
由于ipvs无法提供包过滤、地址伪装、SNAT等功能,所以某些场景下(比如NodePort的实现)还要与iptables搭配使用。
endpoint
endpoint是k8s集群中的一个资源对象,存储在etcd中,用来记录一个 service 对应的所有Pod 的访问地址,一个 Service 由一组 backend Pod 组成。这些 Pod 通过 endpoints 暴露出来,可以说 Endpoint 是实际实现服务的端口的集合。通俗来说,Endpoint 是 service 和 pod 之间的桥梁
kubernetes中的四种port
1)nodePort
nodePort是外部访问k8s集群中service的端口,通过nodeIP: nodePort可以从外部访问到某个service。
2)port
port是k8s集群内部访问service的端口,即通过clusterIP: port可以访问到某个service。
3)targetPort
targetPort是pod的端口,从port和nodePort来的流量经过kube-proxy流入到后端pod的targetPort上,最后进入容器。
4)containerPort
containerPort是pod内部容器的端口,targetPort映射到containerPort。
ingress
Ingress 仅需要一个 NodePort或 LB 就可以满足暴露多个Service的需求
Ingress 则可以调度不同业务域、不同URL访问路径的业务流量
比如:客户端请求 www.kgc.com:port ---> Ingress ---> Service ---> Pod
Ingress就相当于一个7层的负载均衡器,是 K8s 对反向代理的一个抽象,它的工作原理类似于 Nginx,可以理解成在 Ingress 里 建立诸多的隐射规则,然后 Ingress Controller通过监听这些配置规则转化成 Nginx 的反向代理配置,然后对外提供该服务
- Ingress:K8s 中的一个资源对象,作用是定义请求如何转发到 service 的规则
- Ingress Controller:具体实现反向代理及负载均衡的程序,对Ingress定义的规则进行解析,根据配置的规则来实现请求转发,有很多种实现方式,如
Nginx、Contor、Haproxy
等
- Ingress Controller
Service是一种工作于4层的负载均衡器,而Ingress是在应用层实现的HTTP(S)的负载均衡。不过,Ingress资源自身并不能进行流量的穿透,,它仅仅是一组路由规则的集合,这些规则需要通过Ingress控制器(Ingress Controller)发挥作用。目前该功能项目大概有:Nginx-ingress、Traefik、Envoy和HAproxy等 //ingress 暴露服务的方式
●方式一:Deployment+LoadBalancer 模式的 Service 如果要把ingress部署在公有云,那用这种方式比较合适。用Deployment部署ingress-controller,创建一个 type为 LoadBalancer 的 service 关联这组 pod。大部分公有云,都会为 LoadBalancer 的 service 自动创建一个负载均衡器,通常还绑定了公网地址。 只要把域名解析指向该地址,就实现了集群服务的对外暴露
●方式二:DaemonSet+HostNetwork+nodeSelector 用DaemonSet结合nodeselector来部署ingress-controller到特定的node上,然后使用HostNetwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。这时,ingress-controller所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。 比较适合大并发的生产环境使用。
●方式三:Deployment+NodePort模式的Service 同样用deployment模式部署ingress-controller,并创建对应的service,但是type为NodePort。这样,ingress就会暴露在集群节点ip的特定端口上。由于nodeport暴露的端口是随机端口,一般会在前面再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境ip地址不变的场景。 NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。
kubernetes 中的网络
K8S 中 Pod 网络通信:
同一个 Pod 中容器之间的通信
在同一个 Pod 内的容器(Pod 内的容器是不会跨宿主机的)共享同一个网络命名空间,相当于它们在同一台机器上一样,可以用 localhost 地址访问彼此的端口。
同一个 Node 内 Pod 之间的通信
每个 Pod 都有一个真实的全局 IP 地址,同一个 Node 内的不同 Pod 之间可以直接采用对方 Pod 的 IP 地址进行通信,Pod1 与 Pod2 都是通过 Veth 连接到同一个 docker0/cni0 网桥,网段相同,所以它们之间可以直接通信。
不同 Node 上 Pod 之间的通信
Pod 地址与 docker0 在同一网段,docker0 网段与宿主机网卡是两个不同的网段,且不同 Node 之间的通信只能通过宿主机的物理网卡进行。 要想实现不同 Node 上 Pod 之间的通信,就必须想办法通过主机的物理网卡 IP 地址进行寻址和通信。因此要满足两个条件:Pod 的 IP 不能冲突;将 Pod 的 IP 和所在的 Node 的 IP 关联起来,通过这个关联让不同 Node 上 Pod 之间直接通过内网 IP 地址通信。
CNI之flannel
flannel会在每一个宿主机上运行名为flanneld代理,介于docker0与宿主机eth0之间,其负责为宿主机预先分配一个子网,并为Pod分配IP地址。Flannel使用Kubernetes或etcd来存储网络配置、分配的子网和主机公共IP等信息。数据包则通过VXLAN、UDP或host-gw这些类型的后端机制进行转发
flannel三种模式
UDP
* pod1 里的进程发起请求,发出 IP 包;
* IP 包根据 pod1 里的 veth 设备对,进入到网桥设备(docker0/cni0)
* 网桥设备转发到 flannel0 (TUN 设备)中
* flanneld 判断该包应该在哪台 node 上,然后将其封装在一个 UDP 包中(里面有源IP就是pod IP和目的pod IP)
* 通过apiserver进行查询保存在etcd中的的路由表条目
* 物理网卡发送给目的node的flanneld
带来了一次内核态向用户态的转换,以及一次用户态向内核态的转换。在上下文切换和用户态操作的代价其实是比较高
VXLAN
* pod1 里的进程发起请求,发出 IP 包;
* IP 包根据 pod1 里的 veth 设备对,进入到网桥设备(docker0/cni0)
* 网桥设备转发到 flannel.1 (VTEP 设备)中
* flannel.1 将原始 IP 包加上一个目的 MAC 地址,封装成一个二层数据帧;然后内核将数据帧封装进一个 UDP 包里;
* 最后通过 node1 上的网关,发送给 node2;
**与 UDP 模式不一样的是,整个封装的过程是在内核态完成的,没有flanneld参与封装**
flanneld只负责监听和更新etcd里的路由表
host-gw
* pod1 里的进程发起请求,发出 IP 包,从网络层进入链路层封装成帧;
* 根据主机上的路由规则,数据帧从 Node 1 通过宿主机的二层网络到达 Node 2 上
将每个 Flannel 子网的“下一跳”设置成了该子网对应的宿主机的 IP 地址,这台主机会充当这条容器通信路径里的“网关”
各节点必须在同一个2层网络,不支持跨网络,如果有成千上万的Pod,容易产生广播风暴
CNI之Calico
Calico 主要由三个部分组成:
* Calico CNI插件:主要负责与kubernetes对接,供kubelet调用使用。
* Felix:监听etcd,负责维护宿主机上的路由规则、FIB转发信息库等。
* BIRD:负责分发路由规则,类似路由器。
* Confd:配置管理组件。
Calico 工作原理: Calico 的 CNI 插件需要为每个容器设置一个 Veth Pair 设备,然后把其中的一端放置在宿主机上,还需要在宿主机上为每个容器的 Veth Pair 设备配置一条路由规则,用于接收传入的 IP 包
有了这样的 veth pair 设备以后,容器发出的 IP 包就会通过 veth pair 设备到达宿主机,然后宿主机根据路由规则的下一跳地址, 发送给正确的网关,然后到达目标宿主机,再到达目标容器。
IPIP 模式
IPIP 模式为了解决两个 node 不在一个子网的问题。只要将名为 calico-node 的 daemonset 的环境变量 CALICOIPV4POOLIPIP 设置为 "Always" 即可。如下:
- name: CALICO_IPV4POOL_IPIP value: "Off"
IPIP 模式的 calico 使用了 tunl0 设备,这是一个 IP 隧道设备。IP 包进入 tunl0 后,内核会将原始 IP 包直接封装在宿主机的 IP 包中;封装后的 IP 包的目的地址为下一跳地址,即 node2 的 IP 地址。由于宿主机之间已经使用路由器配置了三层转发,所以这个 IP 包在离开 node 1 之后,就可以经过路由器,最终发送到 node 2 上。如下图所示。
由于 IPIP 模式的 Calico 额外多出了封包和拆包的过程,集群的网络性能受到了影响,所以在集群的二层网络通的情况下,建议不要使用 IPIP 模式。
BGP模式
Calico不使用隧道或NAT来实现转发(纯网络层实现),将集群里所有的节点都当做边界路由器来处理,他们一起组成了一个全互联的网络,彼此之间通过 BGP 交换路由,并使用iptables来做安全访问策略, 采用直接路由的方式,这种方式性能损耗最低,不需要修改报文数据,但是如果网络比较复杂场景下,路由表会很复杂,对运维同事提出了较高的要求。
持久化
emptyDir 用于存储临时数据的简单空目录 一个pod中的多个容器需要共享彼此的数据 ,emptyDir的数据随着容器的消亡也会销毁
hostPath将 node 节点的文件系统中的文件或目录挂载到集群中。 不常用,缺点是,pod的调度不是固定的,也就是当pod消失后deployment重新创建一个pod,而这pod如果不是被调度到之前pod的节点,那么该pod就不能访问之前的数据
nfs共享存储卷
PV 全称叫做 Persistent Volume,持久化存储卷。它是用来描述或者说用来定义一个存储卷的,这个通常都是由运维工程师来定义。
pv pvc
PVC 的全称是 Persistent Volume Claim,是持久化存储的请求。它是用来描述希望使用什么样的或者说是满足什么条件的 PV 存储。
PVC 的使用逻辑:在 Pod 中定义一个存储卷(该存储卷类型为 PVC),定义的时候直接指定大小,PVC 必须与对应的 PV 建立关系,PVC 会根据配置的定义去 PV 申请,而 PV 是由存储空间创建出来的。PV 和 PVC 是 Kubernetes 抽象出来的一种存储资源。
要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner
(制备器),这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。
-
- 自动创建的 PV 以
${namespace}-${pvcName}-${pvName}
这样的命名格式创建在 NFS 服务器上的共享数据目录中
- 自动创建的 PV 以
-
- 而当这个 PV 被回收后会以
archieved-${namespace}-${pvcName}-${pvName}
这样的命名格式存在 NFS 服务器上。
- 而当这个 PV 被回收后会以
ConfigMap概述
ConfigMap是k8s的一个配置管理组件,可以将配置以key-value的形式传递,通常用来保存不需要加密的配置信息,加密信息则需用到Secret,主要用来应对以下场景:
-
使用k8s部署应用,当你将应用配置写进代码中,就会存在一个问题,更新配置时也需要打包镜像,ConfigMap可以将配置信息和docker镜像解耦。
-
使用微服务架构的话,存在多个服务共用配置的情况,如果每个服务中单独一份配置的话,那么更新配置就很麻烦,使用ConfigMap可以友好的进行配置共享。
权限管理RBAC
API Server目前支持以下几种授权策略:
* **AlwaysDeny**:表示拒绝所有请求,一般用于测试。
* **AlwaysAllow**:允许接收所有请求。 如果集群不需要授权流程,则可以采用该策略,这也是Kubernetes的默认配置。
* **ABAC**(Attribute-Based Access Control):基于属性的访问控制。 表示使用用户配置的授权规则对用户请求进行匹配和控制。
* Webhook:通过调用外部REST服务对用户进行授权。
* **RBAC**:Role-Based Access Control,基于角色的访问控制(**本章讲解**)。
* **Node**:是一种专用模式,用于对kubelet发出的请求进行访问控制。
kubernetes 特定域名解析方法
添加HostAliases记录
HostAliases是kubernetes中Pod配置的一个字段,它提供了Pod内容器的/etc/hosts文件的附加记录。这在某些情况下非常有用,特别是当你想要覆盖某个主机名的解析结果,或者提供网络中没有的主机名解析时。
这个可以在Pod、Replica、Deployment、StatefulSet的级别修改,维护性稍强。举个 ,我们将上面的yaml修改为
apiVersion: apps/v1
kind: Deployment
metadata:
name: busybox-deployment
labels:
app: busybox
spec:
replicas: 3
selector:
matchLabels:
app: busybox
template:
metadata:
labels:
app: busybox
spec:
hostAliases:
- ip: "250.250.250.250"
hostnames:
- "four-250"
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- "while true; do echo Hello, Kubernetes!; sleep 10;done"
Coredns配置
我们可以通过修改ConfigMap**来实现让容器解析特定域名的目的。
更改Coredns配置
我们可以通过以下命令修改Coredns的配置:
kubectl edit cm coredns -n kube-system
原有的configmap
Corefile: |
.:53 {
log
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
hosts {
192.168.65.2 host.minikube.internal
fallthrough
}
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}
在hosts里面加上特定的记录
250.250.250.250 four-250
如果您没有配置reload插件,则需要重启Coredns才能生效,默认的reload时间是30s,在plugin/reload/setup.go的defaultInterval中定义
自定义DNS策略
通过修改DNS策略。使得对于单个Pod/Deploy/StatefulSet将特定的域名解析发给特定的服务器来达到效果,如下,可以对pod添加dns的服务器以及search域
spec:
dnsConfig:
nameservers:
- 1.2.3.4
searches:
- search.prefix
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- "while true; do echo Hello, Kubernetes!; sleep 10;done"
apiVersion: v1
kind: Pod
metadata:
name: nodejs-app
spec:
containers:
- name: nodejs-app-container
image: nodejs-app-image
ports:
- containerPort: 8080
terminationGracePeriodSeconds: 30 优雅停止时间
## 工作负载管理
### ReplicaSet
`ReplicaSet/ReplicationController` 用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的 Pod 来替代;而如果异常多出来的容器也会自动回收。
ReplicaSet 跟 ReplicationController 没有本质的不同,只是名字不一样,并且 ReplicaSet 支持集合式的 selector。
在新版本的 Kubernetes 中建议使用 ReplicaSet 来取代 ReplicationController。
虽然 ReplicaSet 可以独立使用,但一般还是建议使用 Deployment 来自动管理 ReplicaSet,这样就无需担心跟其他机制的不兼容问题(比如 ReplicaSet 不支持 rolling-update 但 Deployment 支持)。
### Deployment 无状态应用部署
* 支持ReplicaSet的所有功能。
* 支持发布的停止、继续。
* 支持版本滚动更新和版本回退。
* 扩容与缩容
当Deployment 被部署的时候,K8S 会自动生成要求的ReplicaSet 和Pod
- ## API 版本对照表
| Kubernetes 版本 | Deployment 版本 |
| ------------- | ------------------ |
| v1.5-v1.6 | extensions/v1beta1 |
| v1.7-v1.15 | apps/v1beta1 |
| v1.8-v1.15 | apps/v1beta2 |
| v1.9+ | apps/v1
### StatefulSet有状态应用部署
StatefulSet 作为 Controller 为 Pod 提供唯一的标识。它可以保证部署和 scale 的顺序。
与 Deploymnet 相比,Statefulset 具有以下显著的特性:
- 稳定的网络标志
- 稳定的持久化存储
- 自动的滚动升级
- 优雅地部署和扩展
- 优雅地删除和终止
使用 Statefulset 也具有一定的限制:
- StatefulSet 是 beta 资源,Kubernetes 1.5 以前版本不支持。对于所有的 alpha/beta 的资源,您都可以通过在 apiserver 中设置 `--runtime-config` 选项来禁用。
- 给定 Pod 的存储必须由 PersistentVolume Provisioner 根据请求的 `storage class` 进行配置,或由管理员预先配置。
- 删除或 scale StatefulSet 将*不会*删除与 StatefulSet 相关联的 volume。 这样做是为了确保数据安全性,这通常比自动清除所有相关 StatefulSet 资源更有价值。
- StatefulSets 目前要求 [Headless Service](https://kubernetes.io/docs/concepts/services-networking/service/#headless-services) 负责 Pod 的网络身份。
- ## API 版本对照表
| Kubernetes 版本 | Deployment 版本 |
| ------------- | ------------------ |
| v1.5-v1.6 | extensions/v1beta1 |
| v1.7-v1.15 | apps/v1beta1 |
| v1.8-v1.15 | apps/v1beta2 |
| v1.9+ | apps/v1
* pod是独立不一样的,保证pod启动顺序和唯一性
> **在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service**,headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由例如:
Redis,kafka等等软件,客户端需要的是一组pod对应的ip地址,负载均衡是自己控制的
### DaemonSet
DaemonSet 确保全部(或者一些)Node 上运行一个 Pod 的副本。当有 Node 加入集群时,也会为他们新增一个 Pod 。当有 Node 从集群移除时,这些 Pod 也会被回收。删除 DaemonSet 将会删除它创建的所有 Pod。
使用 DaemonSet 的一些典型用法:
- 运行集群存储 daemon,例如在每个 Node 上运行 `glusterd`、`ceph` 的一个副本。
- 在每个 Node 上运行日志收集 daemon,例如`fluentd`、`logstash`。
- 在每个 Node 上运行监控 daemon,例如 [Prometheus Node Exporter](https://github.com/prometheus/node_exporter)、`collectd`、Datadog 代理、New Relic 代理,或 Ganglia `gmond`。
- ### DaemonSet API 版本对照表
| Kubernetes 版本 | Deployment 版本 |
| ------------- | ------------------ |
| v1.5-v1.6 | extensions/v1beta1 |
| v1.7-v1.15 | apps/v1beta1 |
| v1.8-v1.15 | apps/v1beta2 |
| v1.9+ | apps/v1
### Job Controller
Job Controller:监测代表一次性任务的 Job 对象,Pod只要完成任务就立即退出
容器中的进程在正常运行结束后不会对其进行重启,而是将pod对象置于completed状态。若容器中的进程因错误而终止,则需要依据配置确定重启与否,未运行完成的pod对象因其所在的节点故障而意外终止后会被重新调度
简单示例
bash
复制代码
cat << EOF > job-demo.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: job-demo
spec:
template:
metadata:
name: job-demo
spec:
restartPolicy: Never
containers:
- name: counter
image: busybox
command:
- "bin/sh"
- "-c"
- "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done"
EOF
Pod模板中的spec.restartPolicy默认为Always,这对job控制器来说只能设定为Never或OnFailure。
### CronJob
CronJob 管理基于时间的 [Job](https://kubernetes.io/docs/concepts/jobs/run-to-completion-finite-workloads/),即:
- 在给定时间点只运行一次
- 周期性地在给定时间点运行。创建周期性运行的 Job,例如:数据库备份、发送邮件。
> 注意:
>
> Cronjob 管理的对象是 job,job 运行时创建的 pod,cronjob不直接管理 pod。
一个 CronJob 对象类似于 Linux 系统计划任务 *crontab* 文件中的一行。它根据指定的预定计划周期性地运行一个 Job,格式可以参考 [Cron](https://en.wikipedia.org/wiki/Cron) 。
从集群版本 1.8 开始,batch/v2alpha1 API 组中的 CronJob 资源已经被废弃。 你应该切换到 API 服务器默认启用的 batch/v1beta1 API 组。
对于先前版本的集群,版本 < 1.8,启动 API Server(参考 [为集群开启或关闭 API 版本](https://kubernetes.io/docs/admin/cluster-management/#turn-on-or-off-an-api-version-for-your-cluster) 获取更多信息)时,通过传递选项 `--runtime-config=batch/v2alpha1=true` 可以开启 batch/v2alpha1 API。
在 Kubernetes 1.4 版本引入了 ScheduledJob 资源,但从 1.5 版本开始改成了 CronJob。
为 CronJob 资源创建清单时,请确保所提供的名称是一个合法的 DNS 子域名. 名称不能超过 52 个字符。 这是因为 CronJob 控制器将自动在提供的 Job 名称后附加 11 个字符,并且存在一个限制, 即 Job 名称的最大长度不能超过 63 个字符。
- ## API 版本对照表
| Kubernetes 版本 | Batch API 版本 | 默认开启 |
| ------------- | -------------- | ---- |
| v1.5-v1.7 | batch/v2alpha1 | 否 |
| v1.8-v1.9 | batch/v1beta1 | 是 |
注意:使用默认未开启的 API 时需要在 kube-apiserver 中配置 `--runtime-config=batch/v2alpha1`。
apiVersion: batch/v1beta1 kind: CronJob metadata: name: hello namespace: test-ns spec: schedule: "*/1 * * * *" jobTemplate: spec: template: spec: containers: - name: hello image: busybox args: - /bin/sh - -c - date; echo Hello from the Kubernetes cluster restartPolicy: OnFailure