自动化运维之k8s——Kubernetes集群部署、pod、service微服务、kubernetes网络通信
一 Kubernetes 简介及部署方法
1 应用部署方式演变
在部署应用程序的方式上,主要经历了三个阶段:
传统部署:互联网早期,会直接将应用程序部署在物理机上
优点:简单,不需要其它技术的参与
缺点:不能为应用程序定义资源使用边界,很难合理地分配计算资源,而且程序之间容易产生影响
虚拟化部署:可以在一台物理机上运行多个虚拟机,每个虚拟机都是独立的一个环境
优点:程序环境不会相互产生影响,提供了一定程度的安全性
缺点:增加了操作系统,浪费了部分资源
容器化部署:与虚拟化类似,但是共享了操作系统
[!NOTE]
容器化部署方式给带来很多的便利,但是也会出现一些问题,比如说:
一个容器故障停机了,怎么样让另外一个容器立刻启动去替补停机的容器
当并发访问量变大的时候,怎么样做到横向扩展容器数量
2 容器编排应用
为了解决这些容器编排问题,就产生了一些容器编排的软件:
Swarm:Docker自己的容器编排工具
Mesos:Apache的一个资源统一管控的工具,需要和Marathon结合使用
Kubernetes:Google开源的的容器编排工具
3 kubernetes 简介
在Docker 作为高级容器引擎快速发展的同时,在Google内部,容器技术已经应用了很多年
Borg系统运行管理着成千上万的容器应用。
Kubernetes项目来源于Borg,可以说是集结了Borg设计思想的精华,并且吸收了Borg系统中的经验和教训。
Kubernetes对计算资源进行了更高层次的抽象,通过将容器进行细致的组合,将最终的应用服务交给用户。
kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定的程序,来对节点中的容器进行管理。目的是实现资源管理的自动化,主要提供了如下的主要功能:
自我修复:一旦某一个容器崩溃,能够在1秒中左右迅速启动新的容器
弹性伸缩:可以根据需要,自动对集群中正在运行的容器数量进行调整
服务发现:服务可以通过自动发现的形式找到它所依赖的服务
负载均衡:如果一个服务起动了多个容器,能够自动实现请求的负载均衡
版本回退:如果发现新发布的程序版本有问题,可以立即回退到原来的版本
存储编排:可以根据容器自身的需求自动创建存储卷
4 K8S的设计架构
1.4.1 K8S各个组件用途
一个kubernetes集群主要是由控制节点(master)、工作节点(node)构成,每个节点上都会安装不同的组件
1 master:集群的控制平面,负责集群的决策
ApiServer : 资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制
Scheduler : 负责集群资源调度,按照预定的调度策略将Pod调度到相应的node节点上
ControllerManager : 负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等
Etcd :负责存储集群中各种资源对象的信息
2 node:集群的数据平面,负责为容器提供运行环境
kubelet:负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理
Container runtime:负责镜像管理以及Pod和容器的真正运行(CRI)
kube-proxy:负责为Service提供cluster内部的服务发现和负载均衡
1.4.2 K8S 各组件之间的调用关系
当我们要运行一个web服务时
kubernetes环境启动之后,master和node都会将自身的信息存储到etcd数据库中
web服务的安装请求会首先被发送到master节点的apiServer组件
apiServer组件会调用scheduler组件来决定到底应该把这个服务安装到哪个node节点上在此时,它会从etcd中读取各个node节点的信息,然后按照一定的算法进行选择,并将结果告知apiServer
apiServer调用controller-manager去调度Node节点安装web服务
kubelet接收到指令后,会通知docker,然后由docker来启动一个web服务的pod
如果需要访问web服务,就需要通过kube-proxy来对pod产生访问的代理
1.4.3 K8S 的 常用名词感念
Master:集群控制节点,每个集群需要至少一个master节点负责集群的管控
Node:工作负载节点,由master分配容器到这些node工作节点上,然后node节点上的
Pod:kubernetes的最小控制单元,容器都是运行在pod中的,一个pod中可以有1个或者多个容器
Controller:控制器,通过它来实现对pod的管理,比如启动pod、停止pod、伸缩pod的数量等等
Service:pod对外服务的统一入口,下面可以维护者同一类的多个pod
Label:标签,用于对pod进行分类,同一类pod会拥有相同的标签
NameSpace:命名空间,用来隔离pod的运行环境
1.4.4 k8S的分层架构
核心层:Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境
应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS解析等)
管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等)
接口层:kubectl命令行工具、客户端SDK以及集群联邦
生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴
Kubernetes外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS应用、ChatOps等
Kubernetes内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等
二 K8S集群环境搭建
2.1 k8s中容器的管理方式
K8S 集群创建方式有3种:
centainerd
默认情况下,K8S在创建集群时使用的方式
docker
Docker使用的普记录最高,虽然K8S在1.24版本后已经费力了kubelet对docker的支持,但时可以借助cri-docker方式来实现集群创建
cri-o
CRI-O的方式是Kubernetes创建容器最直接的一种方式,在创建集群的时候,需要借助于cri-o插件的方式来实现Kubernetes集群的创建。
[!NOTE]
docker 和cri-o 这两种方式要对kubelet程序的启动参数进行设置
2.2 k8s 集群部署
2.2.1 k8s 环境部署说明
所有节点禁用selinux和防火墙
所有节点同步时间和解析
所有节点安装docker-ce
所有节点禁用swap,注意注释掉/etc/fstab文件中的定义
2.2.2 集群环境初始化
2.2.2.1.所有禁用swap和本地解析
2.2.2.2.所有安装docker
2.2.2.3.所有节点设定docker的资源管理模式为systemd
2.2.2.4.所有阶段复制harbor仓库中的证书并启动docker
2.2.2.5 安装K8S部署工具
2.2.2.6 设置kubectl命令补齐功能
2.2.2.7 在所节点安装cri-docker
2.2.2.8 在master节点拉取K8S所需镜像
2.2.2.9 集群初始化
[!NOTE]
在此阶段如果生成的集群token找不到了可以重新生成
2.2.2.10 安装flannel网络插件
2.2.2.11 节点扩容
在所有的worker节点中
1 确认部署好以下内容
2 禁用swap
3 安装:
kubelet-1.30.0
kubeadm-1.30.0
kubectl-1.30.0
docker-ce
cri-dockerd
4 修改cri-dockerd启动文件添加
--network-plugin=cni
--pod-infra-container-image=reg.timinglee.org/k8s/pause:3.9
5 启动服务
kubelet.service
cri-docker.service
以上信息确认完毕后即可加入集群
在master阶段中查看所有node的状态
!NOTE]
所有阶段的STATUS为Ready状态,那么恭喜你,你的kubernetes就装好了!!
测试集群运行情况
3.Pod管理
Pod是可以创建和管理Kubernetes计算的最小可部署单元,一个Pod代表着集群中运行的一个进程,每个pod都有一个唯一的ip.一个pod类似一个豌豆荚,包含一个或多个容器(常为docker),多个容器间共享IPC、Network和UTC namespace。pod可以提供一个相对持久化的存储环境,当container重置或挂掉时,数据可以暂时存储在pod中,只要pod不出问题,数据也不会有问题。
3.1、pod的创建与删除
注意:使用“kubectl run”命令创建pod时,是自主式的pod,删了就没了;使用“kubectl create deployment”命令创建pod时,会创建一个控制器用于管理此pod。
注意:创建pod后的IP只能在集群内部访问,外部网络无法访问。 因此需要将集群内的pod向集群外暴露一下。
3.2、pod中的端口暴露——service微服务
ervice是一个抽象概念,定义了一个服务的多个pod逻辑合集和访问pod的策略,一般把service称为微服务。
创建service
$ kubectl expose deployment nginx --port=80 --target-port=80
此时pod客户端可以通过service的名称访问后端的两个Pod
ClusterIP: 默认类型,自动分配一个仅集群内部可以访问的虚拟IP
使用NodePort类型暴露端口,让外部客户端访问Pod
$ kubectl edit svc nginx //修改service的type为NodePort
$ kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort //也可以在创建service时指定类型
NodePort: 在ClusterIP基础上为Service在每台机器上绑定一个端口,这样就可以通过 NodeIP:NodePort 来访问该服务
端口暴露后,会向整个pod分配一个IP,在集群内访问此IP,表现的是负载均衡的。这个负载均衡不是通过lvs实现的,二是通过iptables实现,是随机分配的!!!
第一种从外部访问pod的方式:NodePort
为了解决外部网络访问集群节点的问题,还需做以下配置:
将“ClusterIP”更改为“NodePort”,配置完成后,发现my-dep的网络上新增了一个端口“30212”,可以理解为端口映射。
配置完成后在外部网络再次测试,测试时加上映射的端口。可以看到:访问集群中的任何节点都得到同样的结果。
3.3、Pod扩容与缩容
3.4、更新pod镜像
在server2的master端更改完成后,外部网络访问时可以看到版本已经更新了
pod版本回滚后,查看版本更新历史;查看当前pod信息;测试;均可以看到明显效果。
4、资源清单
4.1、资源清单举例
4.2、资源清单中的参数详情
4.3、资源清单示例:
apiVersion: v1
kind: Pod
metadata:name: demo
spec:containers:- name: myapp # 主应用容器image: myapp:v1imagePullPolicy: IfNotPresentports:- name: httpcontainerPort: 80 # 容器监听端口hostPort: 80 # 映射到宿主机的端口resources: # 资源配额配置limits: # 资源上限cpu: 1memory: 200Mirequests: # 资源请求下限cpu: 0.5memory: 100Mi- name: busybox # 辅助工具容器image: busyboxplusimagePullPolicy: IfNotPresentstdin: true # 启用标准输入tty: true # 分配伪终端nodeSelector: # 节点选择器kubernetes.io/hostname: server3hostNetwork: true # 使用主机网络模式
5、Pod生命周期
5.1、Pod生命周期结构图
初始化容器为Init_C,可以为一个,也可以为多个;初始化容器在成功运行后并退出。然后才会运行主容器Main_C;主容器从启动到关闭存在一个生命周期,因此有一个存活探针“Liveness”用来判断主容器是否处于存活状态,容器存活时会显示“running”;就绪探针“readiness”用来探测容器是否准备就绪,准备就绪时会显示“READY”。
Pod 可以包含多个容器,应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点: (1)它们总是运行到完成。 (2)Init 容器不支持 Readiness,因为它们必须在 Pod 就绪之前运行完成,每个 Init 容器必须运行成功,下一个才能够运行。
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy 值为 Never,它不会重新启动。
5.2、Pod的生命周期官方文档
5.3、Init 容器
Init 容器能做什么?
Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。
Init 容器可以安全地运行这些工具,避免这些工具导致应用镜像的安全性降低。
应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
Init 容器能以不同于Pod内应用容器的文件系统视图运行。因此,Init容器可具有访问 Secrets 的权限,而应用容器不能够访问。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。一旦前置条件满足,Pod内的所有的应用容器会并行启动。
5.4、init容器实例
注意:(1)只要yml剧本中存在定义init容器的语句,将会优先执行,无论书写顺序。(2)yml剧本中拉取的镜像会自动从本地仓库中拉取,本地仓库不存在才会去外网拉取,不指定版本的话默认是最新版.
这是因为初始化容器init中缺少微服务,也就是service解析,导致初始化容器无法完成,进而导致主容器nginx也无法启动。
编写svc文件,pod名为“myservice”。编写完成后启动svc.yml。主容器nginx已经可以处于正常运行状态。主容器启动以后,初始化容器就已经停止了。
只要创建了svc,就会在集群中提供解析,svc停止后,解析也没有了!!!因此svc是为初始化容器准备的!!!
测试svc解析:
方法一:
5. 5、探针
检测方式有以下三种:
探针 是由 kubelet 对容器执行的定期诊断。
ExecAction:在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
TCPSocketAction:对指定端口上的容器的 IP 地址进行 TCP 检查。如果端口打开,则诊断被认为是成功的。
HTTPGetAction:对指定的端口和路径上的容器的 IP 地址执行 HTTP Get 请求。如果响应的状态码大于等于200 且小于 400,则诊断被认为是成功的。
每次探测都将获得以下三种结果之一:
成功:容器通过了诊断。 失败:容器未通过诊断。 未知:诊断失败,因此不会采取任何行动。
探针类型有以下三种:
Kubelet 可以选择是否执行在容器上运行的三种探针执行和做出反应
livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其 重启策略 的影响。如果容器不提供存活探针,则默认状态为 Success。
readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容器不提供就绪探针,则默认状态为 Success。
startupProbe: 指示容器中的应用是否已经启动。如果提供了启动探测(startup probe),则禁用所有其他探测,直到它成功为止。如果启动探测失败,kubelet 将杀死容器,容器服从其重启策略进行重启。如果容器没有提供启动探测,则默认状态为成功Success。
重启策略 PodSpec :
中有一个 restartPolicy 字段,可能的值为 Always、OnFailure 和 Never。默认为 Always。
Pod 的生命 :
一般Pod 不会消失,直到人为销毁他们,这可能是一个人或控制器。
建议创建适当的控制器来创建 Pod,而不是直接自己创建 Pod。因为单独的 Pod 在机器故障的情况下没有办法自动复原,而控制器却可以。
三种可用的控制器:
(1)使用 Job 运行预期会终止的 Pod,例如批量计算。Job 仅适用于重启策略为 OnFailure 或 Never 的 Pod。
(2)对预期不会终止的 Pod 使用 ReplicationController、ReplicaSet 和 Deployment ,例如 Web 服务器。ReplicationController 仅适用于具有 restartPolicy 为 Always 的 Pod。
(3)提供特定于机器的系统服务,使用 DaemonSet 为每台机器运行一个 Pod 。
5.1、存活探针和就绪探针实例
存活探针检测的是:镜像启动2秒后检测80端口是否可用,每隔3秒钟检测一次,每次检测时允许有1秒的超时时间
就绪探针检测的是:通过80端口检测默认发布目录下是否存在“test.html”文件,检测规则同上。检测通过后传达就绪指令。
创建一个svc.yml服务,用来暴露pod的端口。微服务通过标签来选择pod,用于向外界展示集群中的端口,当pod状态没有就绪之前,pod不会被微服务发现。微服务具有自动发现的功能,并实现后端的负载均衡。
由于就绪探针无法检测到nginx发布目录的“test.html”文件,所以pod还无法运行,进入pod后(pod名为liveness-http)在发布目录中创建“test.html”文件,再次查看pod状态,已经准备就绪。通过curl命令检查,也已经就绪了。一旦pod准备就绪,就会被myservice的svc自动发现。
注意:就绪探针会一直存在,当就绪探针检测的条件不满足时,会立即停止pod服务,直到再次检测到条件满足时才会允许pod服务运行。 微服务用于发现准备就绪的pod并实现后端的负载均衡。
六、控制器
6.1、控制器的基本概念
Pod 的分类:
(1)自主式 Pod:由用户管理,Pod 退出后不会被创建
(2)控制器管理的 Pod:在控制器的生命周期里,始终要维持 Pod 的副本数目
控制器类型:Replication Controller和ReplicaSet、 Deployment、 DaemonSet、 StatefulSet、 Job、 CronJob、 HPA(Horizontal Pod Autoscaler)
控制器类型:
6. 2、控制器的官方文档
6.3、ReplicaSet控制器示例——容器数量检测(容器挂掉时自动创建)
rs管理container的依据是“标签项”,只有符合“app=nginx”的标签才属于rs管理。更改pod标签后,原有的container虽然ID没有变化,但是已经脱离了rs的管理,而rs为了保证有三个副本,会再创建一个container。
6.4、Deployment控制器示例——版本回滚
Deployment控制器是在rs控制器基础之上建立的。基础功能同rs控制器一样,以container的标签为标准。更改container标签后,也会创建新的容器。
Deployment的主要功能是镜像版本的变更(升级或回滚)
更改镜像版本后,再次生效,会发现控制器后会有历史信息。
更改回之前的镜像版本,容器可以直接回滚为之前的版本!!!
Deployment的基本结构图如下所示:
当版本从v1变为v2时,会新建一个rs控制器,并通过v2的rs控制器创建新的容器,同时保留v1的rs控制器;版本要回滚到v1时,会直接从v1的rs控制器上创建容器,极大节省了资源。
6. 5、DaemonSet控制器示例——确保全部(或者某些)节点上运行一个 Pod 的副本
这里不需要设置副本数量,它会自动为每个节点添加一个镜像。
6.6、Job控制器示例——执行批处理任务,仅执行一次任务,做完以后就退出了
6.7、Cronjob控制器示例——CronJob创建定时任务,周期性地在给定的调度时间执行Job
每隔一分钟执行一次,输出一次语句。
7、service——微服务
7.1、Service概念
Service可以看作是一组提供相同服务的Pod对外的访问接口(用于pod内端口暴露)。借助Service,应用可以方便地实现服务发现和负载均衡。
service默认只支持4层负载均衡能力,没有7层功能。(可以通过Ingress实现)
service的类型:
ClusterIP:默认值,k8s系统给service自动分配的虚拟IP,只能在集群内部访问。
NodePort:将Service通过指定的Node上的端口暴露给外部,访问任意一个
NodeIP:nodePort都将路由到ClusterIP。
LoadBalancer:在 NodePort 的基础上,借助 cloud provider 创建一个外部的负载均衡器,并将请求转发到 <NodeIP>:NodePort,此模式只能在云服务器上使用。
ExternalName:将服务通过 DNS CNAME 记录方式转发到指定的域名(通过 spec.externlName 设定)。
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。
kube-proxy 通过 iptables 处理 Service 的过程,需要在宿主机上设置相当多的 iptables 规则,如果宿主机有大量的Pod(以及增删pod时),不断刷新iptables规则,会消耗大量的CPU资源。
IPVS模式的service,可以使K8s集群支持更多量级的Pod。
开启kube-proxy的ipvs模式:
更新完成后,通过ipvsadm看到策略已经更新,如下所示:
并且设置完成后,宿主机的网卡选项会多出来kube-ipvs的接口。所有增加的svc的地址都会增加到这个接口的IP上
7.2、deplyment控制器+svc服务
[root@server2 pod]# vim deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:name: deployment-nginx
spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: myapp:v1---
# 创建微服务Service
apiVersion: v1
kind: Service
metadata:name: myservice
spec:selector:app: nginx # 注意标签需与deployment控制器定义的标签保持一致ports:- protocol: TCPport: 80 # 服务端口targetPort: 80 # pod/容器内端口
通过ipvsadm查看转发规则,svc获取到的虚拟IP地址可以通过rr算法调度到后台的三个容器中。
这样就可以在实现负载均衡的同时,不用频繁地刷新iptables策略,会随着容器的IP动态变更
8、k8s网络通信
k8s通过CNI接口接入其他插件来实现网络通讯。目前比较流行的插件有flannel,calico等。 CNI插件存放位置:# cat /etc/cni/net.d/10-flannel.conflist
插件使用的解决方案如下:
虚拟网桥,虚拟网卡,多个容器共用一个虚拟网卡进行通信。
多路复用:MacVLAN,多个容器共用一个物理网卡进行通信。
硬件交换:SR-LOV,一个物理网卡可以虚拟出多个接口,这个性能最好。
容器间通信:同一个pod内的多个容器间的通信,通过lo即可实现;
pod之间的通信:
同一节点的pod之间通过cni网桥转发数据包。
不同节点的pod之间的通信需要网络插件支持。
pod和service通信: 通过iptables或ipvs实现通信,ipvs取代不了iptables,因为ipvs只能做负载均衡,而做不了nat转换。
pod和外网通信:iptables的MASQUERADE。
Service与集群外部客户端的通信:(ingress、nodeport、loadbalancer)
8.1、Flannel vxlan模式跨主机通信原理
8.2、flannel网络
当容器发送IP包,通过veth pair 发往cni网桥,再路由到本机的flannel.1设备进行处理。
VTEP设备之间通过二层数据帧进行通信,源VTEP设备收到原始IP包后,在上面加上一个目的MAC地址,封装成一个内部数据帧,发送给目的VTEP设备。
内部数据桢,并不能在宿主机的二层网络传输,Linux内核还需要把它进一步封装成为宿主机的一个普通的数据帧,承载着内部数据帧通过宿主机的eth0进行传输。
Linux会在内部数据帧前面,加上一个VXLAN头,VXLAN头里有一个重要的标志叫VNI,它是VTEP识别某个数据桢是不是应该归自己处理的重要标识。
flannel.1设备只知道另一端flannel.1设备的MAC地址,却不知道对应的宿主机地址是什么。在linux内核里面,网络设备进行转发的依据,来自FDB的转发数据库,这个flannel.1网桥对应的FDB信息,是由flanneld进程维护的。
linux内核在IP包前面再加上二层数据帧头,把目标节点的MAC地址填进去,MAC地址从宿主机的ARP表获取。
此时flannel.1设备就可以把这个数据帧从eth0发出去,再经过宿主机网络来到目标节点的eth0设备。目标主机内核网络栈会发现这个数据帧有VXLAN Header,并且VNI为1,Linux内核会对它进行拆包,拿到内部数据帧,根据VNI的值,交给本机flannel.1设备处理,flannel.1拆包,根据路由表发往cni网桥,最后到达目标容器。
配置flannel:
8.3、calico网络插件(详细信息在后边介绍)
官网:https://docs.projectcalico.org/getting-started/kubernetes/self-managed-onprem/onpremises
calico简介:
flannel实现的是网络通信,calico的特性是在pod之间的隔离。
通过BGP路由,但大规模端点的拓扑计算和收敛往往需要一定的时间和计算资源。
纯三层的转发,中间没有任何的NAT和overlay,转发效率最好。
Calico 仅依赖三层路由可达。Calico 较少的依赖性使它能适配所有 VM、Container、白盒或者混合环境场景。
calico网络架构:
Felix:监听ECTD中心的存储获取事件,用户创建pod后,Felix负责将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。同样如果用户制定了隔离策略,Felix同样会将该策略创建到ACL中,以实现隔离。
BIRD:一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,路由的时候到这里来。
8.4、Headless Service “无头服务”
Headless Service不需要分配一个VIP,而是直接以DNS记录的方式解析出被代理Pod的IP地址。 域名格式:$(servicename).$(namespace).svc.cluster.local
Headless Service实例:
8.5、第二种从外部访问 Service 的方式LoadBalancer 类型的 Service
从外部访问 Service 的第二种方式,适用于公有云上的 Kubernetes 服务。这时候,你可以指定一个 LoadBalancer 类型的 Service。(第一种是NodePort方式,第二种是LoadBalancer方式)
在service提交后,Kubernetes就会调用 CloudProvider 在公有云上为你创建一个负载均衡服务,并且把被代理的 Pod 的 IP地址配置给负载均衡服务做后端。在本例中的“EXTERNAL-IP”可以理解为一个负载均衡器。
8.6、在单机上部署Metallb-layer2环境
先启动一个svc服务,在其中添加了deployment控制器控制的nginx镜像,便于后续测试。
# LoadBalancer Service configuration
apiVersion: v1
kind: Service
metadata:name: lb-nginx
spec:ports:- name: httpport: 80targetPort: 80selector:app: nginxtype: LoadBalancer---
# Deployment configuration
apiVersion: apps/v1
kind: Deployment
metadata:name: deployment-nginx
spec:replicas: 3selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: nginximage: myapp:v1
部署metallb-layer2
注意:部署metallb时 ,metallb.yaml文件中会有拉取镜像,所以可以先将镜像下载到本地仓库中
配置地址池:
测试:svc获得了一个“EXTERNAL-IP”后,外部网络就可以直接访问这个IP来访问后台的容器,并且访问是负载均衡的。测试结果如下
8.7、第三种从外部访问service的方式ExternalName
由于svc在集群内的IP地址总是在变化,因此可以提供一个解析使其相对固定下来;外部网络访问时只需要去找域名就可以自动解析到svc的IP。
也可以直接为svc绑定一个IP,svc启动后,需要手工向节点中加上一个IP地址,然后集群外部就可以访问,一般不推荐,这对后期的维护成本要求较高。
8.8、Ingress 服务——从外网访问集群内部
一种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的Ingress 服务。
Ingress由两部分组成:Ingress controller和Ingress服务。 Ingress Controller 会根据你定义的 Ingress 对象,提供对应的代理能力。
业界常用的各种反向代理项目,比如 Nginx、HAProxy、Envoy、Traefik 等,都已经为Kubernetes 专门维护了对应的 Ingress Controller。
(1)基本ingress结构的部署步骤:
在server2中的deploy.yaml文件中镜像地址改为私有仓库的地址。
部署完成后效果如下:
基本逻辑是:client——>ingress——>svc——>pod。因此可以将ingress控制器改为LoadBalancer模式,会自动获取到一个IP地址,就可以从外部网络直接访问svc了。
先在的需求是,在外部主机访问“www1.westos.org”域名就可以通过ingress的方式访问到pod内部。因此需要重新设置一个svc
图绿框中表明,当外部网络访问“www1.westos.org”时,会自动访问后端“myservice:80”上的3个pod
在外部主机测试:
(2)ingress部署扩展——基于域名的虚拟主机
为了和一步实验稍作区分,这里新建一个实验域名为“www2.westos.org”,并且svc服务名称为“myapp-svc”。
apiVersion: apps/v1
kind: Deployment
metadata:name: deployment-myapp
spec:replicas: 3selector:matchLabels:app: myapptemplate:metadata:labels:app: myappspec:containers:- name: myappimage: myapp:v2
---
apiVersion: v1
kind: Service
metadata:name: myapp-svc
spec:selector:app: myappports:- protocol: TCPport: 80 # 服务端口targetPort: 80 # Pod容器端口
#在之前的ingress.yaml文件中添加www2.westos.org的域名
[root@server2 ingress]# vim ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-demo
spec:ingressClassName: nginxrules:- host: www1.westos.orghttp:paths:- path: / # 根目录访问pathType: Prefixbackend: # 后端服务配置service:name: myservice # 匹配的service名称port:number: 80- host: www2.westos.orghttp:paths:- path: / # 根目录访问pathType: Prefixbackend: # 后端服务配置service:name: myapp-svc # 匹配的service名称port:number: 80
测试:在外部主机上分别访问这两个域名,会被ingress调度到不同的svc上
(3)ingress examples
(4)ingress加密
创建证书并导入kubectl
[root@server2 ingress]# vim ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-demo
spec:tls: # TLS 加密配置- hosts:- www1.westos.orgsecretName: tls-secret # 指定 TLS 证书的 SecretingressClassName: nginxrules:- host: www1.westos.orghttp:paths:- path: / # 根路径路由pathType: Prefixbackend:service:name: myservice # 后端服务名称port:number: 80- host: www2.westos.orghttp:paths:- path: / # 根路径路由pathType: Prefixbackend:service:name: myapp-svc # 后端服务名称port:number: 80
测试:当外部主机访问80端口时,会自动重定向到ingress的443端口
(5)ingress认证
生成认证文件并导入kubectl资源中
在ingress.yaml中添加用户认证的选项
[root@server2 ingress]# vim ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:name: ingress-demoannotations:nginx.ingress.kubernetes.io/auth-type: basicnginx.ingress.kubernetes.io/auth-secret: basic-authnginx.ingress.kubernetes.io/auth-realm: 'Authentication Required - admin'
spec:tls:- hosts:- www1.westos.orgsecretName: tls-secretingressClassName: nginxrules:- host: www1.westos.orghttp:paths:- path: /pathType: Prefixbackend:service:name: myserviceport:number: 80- host: www2.westos.orghttp:paths:- path: /pathType: Prefixbackend:service:name: myapp-svcport:number: 80
重新运行ingress.yaml文件后,可以看到认证信息。意味着访问这个域名需要认证。
测试:
(6)ingress重定向
在ingress.yaml文件中添加重定向语句,格式如下:
测试:设置好后,通过浏览器测试,输入“www1.westos.org”时会自动重定向到“www1.westos.org/hostname.html”
9、金丝雀(canary)发布实践
在之前实验的基础上,将两个svc的名称分别更改为“myapp-v1”和“myapp-v2”,用于模拟两套版本,查看版本迭代效果。(删掉之前的ingress,防止对本实验有影响)
“myapp-v1”版本如下:
令其生效后,在外部主机访问,可以看到当前镜像版本为“mapp-v1”
如果现在需要更新到“myapp-v2”版本,用金丝雀的方式让v1的流量慢慢移动到v2上。
在宿主机测试,大约是每访问10次就会有一次的被分配到v2上
接下来将流量比例调整为50,意味着每访问100次,就有50次被分配到v2上。在宿主机通过一个脚本实现,脚本内容如下:
10、calico网络插件
如果想要在集群中控制某些服务对网络的访问,需要设定相应的网络策略。设定网络策略需要网络插件的支持。
而之前用到的"flannel网络插件"无法提供这样的服务,因此需要更换可以提供此服务的网络插件“calico网络插件”。
(1)calico简介:
flannel实现的是网络通信,calico的特性是在pod之间的隔离。
通过BGP路由,但大规模端点的拓扑计算和收敛往往需要一定的时间和计算资源。
纯三层的转发,中间没有任何的NAT和overlay,转发效率最好。
Calico 仅依赖三层路由可达。Calico 较少的依赖性使它能适配所有 VM、Container、白盒或者混合环境场景。
(2)calico网络架构
Felix:监听ECTD中心的存储获取事件,用户创建pod后,Felix负责将其网卡、IP、MAC都设置好,然后在内核的路由表里面写一条,注明这个IP应该到这张网卡。同样如果用户制定了隔离策略,Felix同样会将该策略创建到ACL中,以实现隔离。
BIRD:一个标准的路由程序,它会从内核里面获取哪一些IP的路由发生了变化,然后通过标准BGP的路由协议扩散到整个其他的宿主机上,让外界都知道这个IP在这里,路由的时候到这里来。
(3)calico安装及部署
为了不受之前实验的干扰,删除掉之前所有的pod
然后重新新建一个项目。并且在集群内测试已经没有问题了。
要从集群外部访问还需要一个ingress,所以就启动一个之前的“v1-ingress.yaml”
测试:在外部主机上访问集群内部,已经可以正常访问了 。因此证明calico已经安装完成
11、k8s调度(Scheduling)
1.调度在Kubernetes中的作用
调度是指将未调度的Pod自动分配到集群中的节点的过程
调度器通过 kubernetes 的 watch 机制来发现集群中新创建且尚未被调度到 Node 上的 Pod
调度器会将发现的每一个未调度的 Pod 调度到一个合适的 Node 上来运行
2. 调度原理
创建Pod:用户通过Kubernetes API创建Pod对象,并在其中指定Pod的资源需求、容器镜像等信息。
调度器监视Pod: Kubernetes调度器监视集群中的未调度Pod对象,并为其选择最佳的节点。
选择节点:调度器通过算法选择最佳的节点,并将Pod绑定到该节点上。调度器选择节点的依据包括节点的资源使用情况、Pod的资源需求、亲和性和反亲和性等。
绑定Pod到节点: 调度器将Pod和节点之间的绑定信息保存在etcd数据库中,以便节点可以获取Pod的调度信息。
节点启动Pod: 节点定期检查etcd数据库中的Pod调度信息,并启动相应的Pod。如果节点故障或资源不足,调度器会重新调度Pod,并将其绑定到其他节点上运行。
默认调度器(Default Scheduler): 是Kubernetes中的默认调度器,负责对新创建的Pod进行调度,并将Pod调度到合适的节点上。
自定义调度器(Custom Scheduler): 是一种自定义的调度器实现,可以根据实际需求来定义调度策略和规则,以实现更灵活和多样化的调度功能。
扩展调度器(Extended Scheduler): 是一种支持调度器扩展器的调度器实现,可以通过调度器扩展器来添加自定义的调度规则和策略,以实现更灵活和多样化的调度功能。
kube-scheduler是kubernetes中的默认调度器,在kubernetes运行后会自动在控制节点运行
3. 常用调度方法
nodeName 是节点选择约束的最简单方法,但一般不推荐
如果 nodeName 在 PodSpec 中指定了,则它优先于其他的节点选择方法
使用 nodeName 来选择节点的一些限制
如果指定的节点不存在。
如果指定的节点没有资源来容纳 pod,则pod 调度失败。
云环境中的节点名称并非总是可预测或稳定的
4.Nodeselector(通过标签控制节点)
nodeSelector 是节点选择约束的最简单推荐形式
给选择的节点添加标签:kubectl label nodes k8s-node1 lab=lee
可以给多个节点设定相同标签
5.affinity(亲和性)
1 亲和与反亲和
nodeSelector 提供了一种非常简单的方法来将 pod 约束到具有特定标签的节点上。亲和/反亲和功能极大地扩展了你可以表达约束的类型。
使用节点上的 pod 的标签来约束,而不是使用节点本身的标签,来允许哪些 pod 可以或者不可以被放置在一起。
2 nodeAffinity节点亲和
那个节点服务指定条件就在那个节点运行
requiredDuringSchedulingIgnoredDuringExecution 必须满足,但不会影响已经调度
preferredDuringSchedulingIgnoredDuringExecution 倾向满足,在无法满足情况下也会调度pod
IgnoreDuringExecution 表示如果在Pod运行期间Node的标签发生变化,导致亲和性策略不能满足,则继续运行当前的Pod。
nodeaffinity还支持多种规则匹配条件的配置如
Node Affinity
3. Podaffinity(pod的亲和)
那个节点有符合条件的POD就在那个节点运行
podAffinity 主要解决POD可以和哪些POD部署在同一个节点中的问题
podAntiAffinity主要解决POD不能和哪些POD部署在同一个节点中的问题。它们处理的是Kubernetes集群内部POD和POD之间的关系。
Pod 间亲和与反亲和在与更高级别的集合(例如 ReplicaSets,StatefulSets,Deployments 等)一起使用时
Pod 间亲和与反亲和需要大量的处理,这可能会显著减慢大规模集群中的调度。
Podaffinity示例
4.Taints(污点模式,禁止调度)
Taints(污点)是Node的一个属性,设置了Taints后,默认Kubernetes是不会将Pod调度到这个Node上
Kubernetes如果为Pod设置Tolerations(容忍),只要Pod能够容忍Node上的污点,那么Kubernetes就会忽略Node上的污点,就能够(不是必须)把Pod调度过去
可以使用命令 kubectl taint 给节点增加一个 taint:
其中[effect] 可取值:
Taints示例
tolerations(污点容忍)
tolerations中定义的key、value、effect,要与node上设置的taint保持一直:
如果 operator 是 Equal ,则key与value之间的关系必须相等。
如果 operator 是 Exists ,value可以省略
如果不指定operator属性,则默认值为Equal。
还有两个特殊值:
当不指定key,再配合Exists 就能匹配所有的key与value ,可以容忍所有污点。
当不指定effect ,则匹配所有的effect
污点容忍示例:
12、 kubernetes API 访问控制
Authentication(认证)
认证方式现共有8种,可以启用一种或多种认证方式,只要有一种认证方式通过,就不再进行其它方式的认证。通常启用X509 Client Certs和Service Accout Tokens两种认证方式。
Kubernetes集群有两类用户:由Kubernetes管理的Service Accounts (服务账户)和(Users Accounts) 普通账户。k8s中账号的概念不是我们理解的账号,它并不真的存在,它只是形式上存在。
Authorization(授权)
必须经过认证阶段,才到授权请求,根据所有授权策略匹配请求资源属性,决定允许或拒绝请求。授权方式现共有6种,AlwaysDeny、AlwaysAllow、ABAC、RBAC、Webhook、Node。默认集群强制开启RBAC。
Admission Control(准入控制)
用于拦截请求的一种方式,运行在认证、授权之后,是权限认证链上的最后一环,对请求API资源对象进行修改和校验。
1. UserAccount与ServiceAccount
用户账户是针对人而言的。 服务账户是针对运行在 pod 中的进程而言的。
用户账户是全局性的。 其名称在集群各 namespace 中都是全局唯一的,未来的用户资源不会做 namespace 隔离, 服务账户是 namespace 隔离的。
集群的用户账户可能会从企业数据库进行同步,其创建需要特殊权限,并且涉及到复杂的业务流程。 服务账户创建的目的是为了更轻量,允许集群用户为了具体的任务创建服务账户 ( 即权限最小化原则 )。
2. ServiceAccount
服务账户控制器(Service account controller)
服务账户管理器管理各命名空间下的服务账户
每个活跃的命名空间下存在一个名为 “default” 的服务账户
服务账户准入控制器(Service account admission controller)
相似pod中 ServiceAccount默认设为 default。
保证 pod 所关联的 ServiceAccount 存在,否则拒绝该 pod。
如果pod不包含ImagePullSecrets设置那么ServiceAccount中的ImagePullSecrets 被添加到pod中
将挂载于 /var/run/secrets/kubernetes.io/serviceaccount 的 volumeSource 添加到 pod 下的每个容器中.
将一个包含用于 API 访问的 token 的 volume 添加到 pod 中。
3.ServiceAccount示例:
建立名字为admin的ServiceAccount
建立secrets
将secrets注入到sa中
建立私有仓库并且利用pod访问私有仓库
pod绑定sa
13.认证(在k8s中建立认证用户)
1 创建UserAccount
2 RBAC(Role Based Access Control)
1 基于角色访问控制授权:
允许管理员通过Kubernetes API动态配置授权策略。RBAC就是用户通过角色与权限进行关联。
RBAC只有授权,没有拒绝授权,所以只需要定义允许该用户做什么即可
RBAC的三个基本概念:Subject:被作用者,它表示k8s中的三类主体, user, group, serviceAccount
Role:角色,它其实是一组规则,定义了一组对 Kubernetes API 对象的操作权限
RoleBinding:定义了“被作用者”和“角色”的绑定关系
RBAC包括四种类型:Role、ClusterRole、RoleBinding、ClusterRoleBinding
Role 和 ClusterRole: Role是一系列的权限的集合,Role只能授予单个namespace 中资源的访问权限。
ClusterRole 跟 Role 类似,但是可以在集群中全局使用。
Kubernetes 还提供了四个预先定义好的 ClusterRole 来供用户直接使用
cluster-amdin、admin、edit、view
2 role授权实施
3 clusterrole授权实施
4 服务账户的自动化
服务账户准入控制器(Service account admission controller)
如果该 pod 没有 ServiceAccount 设置,将其 ServiceAccount 设为 default。
保证 pod 所关联的 ServiceAccount 存在,否则拒绝该 pod。
如果 pod 不包含 ImagePullSecrets 设置,那么 将 ServiceAccount 中的 ImagePullSecrets 信息添加到 pod 中。
将一个包含用于 API 访问的 token 的 volume 添加到 pod 中。
将挂载于 /var/run/secrets/kubernetes.io/serviceaccount 的 volumeSource 添加到 pod 下的每个容器中。
服务账户控制器(Service account controller)
服务账户管理器管理各命名空间下的服务账户,并且保证每个活跃的命名空间下存在一个名为 “default” 的服务账户