k8s学习笔记
2023-02-21 00:23:15 5 举报
AI智能生成
Kubernetes学习思维导图
作者其他创作
大纲/内容
深入分析集群安全机制
集群安全性应该考虑的目标
保证容器与其所在宿主机的隔离
限制容器给基础设施或者其他容器带来的干扰
最小权限原则,即合理限制所有组件的权限,确保组件只执行它被授权的行为,通过限制单个组件的能力来限制它的权限范围
明确组件间边界的划分
划分普通用户跟管理员用户
在必要时允许将管理员权限赋给普通用户
允许拥有Secret数据、的应用在集群中允许
API Server 认证管理
K8S的两种类型账号
集群内部账号Service Account(源码那也有说)
外部的用户账号
访问k8s API的三种方式
以证书的形式访问的普通用户或进程,包括运维人员及kubectl、kubelets等进程
以SA方式访问k8s的内部服务进程,(部署在k8s上的pod)
以匿名方式访问的进程
k8s集群提供的用户身份认证方式
HTTPS证书认证:基于CA根证书签名的双向数字证书认证方式
子主题
HTTP Bearer Token 第三方认证:通过一个Bearer Token识别合法用户
子主题
OpenID Connect Token认证:通过第三方OIDC协议进行认证
子主题
Webhook Token认证:通过外部Webhook服务进行认证
子主题
API Server授权管理
授权管理:<br>1、当外部的客户端 发起API Server调用时,API Server内部要先进行用户认证,然后执行用户授权流程,即通过授权策略(Authoritarian policy)决定一个API调用是否合法。对合法的用户进行授权后在用户访问时进行授权<br>2、简单的说就是,给不同的用户不同的权限
授权策略
AlwaysDeny:表示拒绝所有请求,仅用于测试
废弃
AlwaysAllow:允许接收所有请求,如果集群不需要授权流程,则可以采用该策略
ABAC:基于属性的访问控制,表示使用用户配置的授权规则对用户的请求进行匹配跟控制
废弃
Webhook:通过调用外部的REST服务对用户进行授权
外部的服务用
Node:是对于kubelet进程授权的特殊模式
控制节点用,使用kubectl的时候
RBAC:基于角色的访问控制
cat /etc/kubernetes/manifests/kube-apiserver.yaml
重点授权策略讲解
RBAC
RBAC优点
对集群的资源和非资源权限均有完整的覆盖
RBAC的权限配置通过几个API对象即可完成,同其他API对象一样,可用kubectl、或者API进行操作
可以在运行时进行调整(kubectl edit),无需重启API Server
RBAC资源对象操作
yaml文件编写操作
创建用户凭证<br><b><font color="#d32f2f">创建好以后/root/.kube/config配置文件会多一个用户<br></font></b>https://www.cnblogs.com/fat-girl-spring/p/14586259.html<br>
1、给用户 cnych 创建一个私钥,命名成 cnych.key<br>openssl genrsa -out cnych.key 2048<br><br>2、使用我们刚刚创建的私钥创建一个证书签名请求文件:cnych.csr,要注意需要确保在-subj参数中指定用户名和组(CN表示用户名,O表示组)<br>openssl req -new -key cnych.key -out cnych.csr -subj "/CN=cnych/O=youdianzhishi"<br><br>3、然后找到我们的 Kubernetes 集群的 CA 证书,我们使用的是 kubeadm 安装的集群,CA 相关证书位于 /etc/kubernetes/pki/ 目录下面,利用该目录下面的 ca.crt 和 ca.key两个文件来批准上面的证书请求。生成最终的证书文件,我们这里设置证书的有效期为 500 天<br>openssl x509 -req -in cnych.csr -CA /etc/kubernetes/pki/ca.crt -CAkey /etc/kubernetes/pki/ca.key -CAcreateserial -out cnych.crt -days 500<br><br>4、查看我们当前文件夹下面是否生成了一个证书文件<br>$ ls<br>cnych.crt cnych.csr cnych.key<br><br>5、使用刚刚创建的证书文件和私钥文件在集群中创建新的凭证和上下文(Context):<br>kubectl config set-context cnych-context --cluster=kubernetes --namespace=kube-system --user=cnych<br>
如何理解角色跟集群角色
<b>1、一个角色就是一组权限的集合,在Role中设置的权限都是许可(Permissive)形式的,不可设置拒绝(Deny)形式的规则<br>2、Role设置的权限都会局限于名称空间(namespace)范围内<br>3、如果需要设置集群级别的权限设置,就需要使用ClusterRole了</b>
Role(角色)<br>https://github.com/tzh666/k8s-yaml/blob/main/rbac/role-demo.yaml<br>
ClusterRole(集群角色)<br>https://github.com/tzh666/k8s-yaml/blob/main/rbac/cluster-role-demo.yaml<br>
参数说明
resources:需要操作的资源对象类型列表,例如pods、deployments、jobs等
apiGroup:资源对象API组列表,例如Core、extensions、apps、batch等
verbs:设置允许对资源对象操作的方法列表,例如 get、watch、list、delete、patch等
角色绑定和集群角色绑定理解
<b>1、角色绑定和集群角色绑定用来把一个角色绑定到一个目标上,<font color="#f44336">绑定目标可以是User、Group、SA<br></font>2、RoleBinding用于某个名称空间的授权<br>3、ClusterRoleBinding用于集群范围的授权</b>
角色绑定
<b>1、角色绑定可以与属于同一个名称空间的Role绑定 </b>或者<font color="#f44336">某个集群级别的ClusterRole绑定</font>,完成对某个主体的授权
# 同一个ns中的Role进行绑定, 此绑定完成的授权: 允许用户jane读取ns default的Pod资源信息<br>https://github.com/tzh666/k8s-yaml/blob/main/rbac/rolebinding-demo-1.yaml<br>
# 给用户dava授权一个ClusterRole "secret-reader"的集群对象,但因为RoleBinding的作用范围为名称空间devement,所以用户只能读取该ns下的secret资源对象,而不能读取其他ns下的secret资源对象
集群角色绑定
1、集群角色绑定只能引用集群级别的角色,而不能是名称空间级别的Role<br>2、如果创建了某个RoleBinding或者CLusterRoleBinding与某个Role或ClusterROle完成了绑定,不能edit修改,只能先删,再apply
参数解释
# 主题,对应集群中尝试操作的对象,集群中定义了3种类型的主题资源<br>Subject<br>
User Account:用户,这是有外部独立服务进行管理的,管理员进行私钥的分配,用户可以使用 KeyStone 或者 Goolge 帐号,甚至一个用户名和密码的文件列表也可以。对于用户的管理集群内部没有一个关联的资源对象,所以用户不能通过集群内部的 API 来进行管理
Group:组,这是用来关联多个账户的,集群中有一些默认创建的组,比如 cluster-admin
Service Account:服务帐号,通过 Kubernetes API 来管理的一些用户帐号,和 namespace 进行关联的,适用于集群内部运行的应用程序,需要通过 API 来完成权限认证,所以在集群内部进行权限操作,我们都需要使用到 ServiceAccount,这也是我们这节课的重点
RoleBinding 和 ClusterRoleBinding:
角色绑定和集群角色绑定,简单来说就是把声明的 Subject 和我们的 Role 进行绑定的过程(给某个用户绑定上操作的权限)
二者的区别也是作用范围的区别:<font color="#d32f2f"><b>RoleBinding 只会影响到当前 namespace 下面的资源操作权限,而 ClusterRoleBinding 会影响到所有的 namespace</b></font>
Rule:规则,规则是一组属于不同 API Group 资源上的一组操作的集合
Role 和 ClusterRole:角色和集群角色,这两个对象都包含上面的 Rules 元素,二者的区别在于,在 Role 中,定义的规则只适用于单个命名空间,也就是和 namespace 关联的,而 ClusterRole 是集群范围内的,因此定义的规则不受命名空间的约束
ClusterRole 在Kubernetes 中都被定义为集群内部的 API 资源,和我们前面学习过的 Pod、Deployment 这些对象类似,都是我们集群的资源对象,所以同样的可以使用 YAML 文件来描述,用 kubectl 工具来管理
kubectl 命令行操作
kubectl 操作
# 在ns范围内创建授权规则<br>1、创建名为 "pod-reader"的Role,允许对Pod进行get、list、watch操作<br>kubectl create role pod-reader --verb=get --verb=list --verb=watch --resource=pods<br><br>2、允许对特定名称(resourceNames)的Pod进行get操作<br>kubectl create role pod-reader-2 --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod<br><br>3、允许对于API 中的replicaset 进行get watch list操作<br>kubectl create role foo --verb=get,list,watch --resource=replicasets.apps<br>
# 创建集群ClusterRole: kubectl create clusterrole<br>1、创建名字为 "pod-reader"的ClusterRole,允许对所有ns中的Pod镜像get list wtach操作<br>kubectl create clusterrole pod-reader-2 --verb=get,list,watch --resource=pods<br><br>2、允许对特定名称的Pod(resourceNamws)进行get操作<br>kubectl create clusterrole pod-reader-1 --verb=get --resource=pods --resource=replicasets.apps<br>
# 创建RoleBinding kubectl create rolebinding<br>1、在特定的名称空间中为用户 bob 授权 ClusterBind "admin":<br>kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob --namespace=default<br><br>2、为特定的ns中sa "myapp" 授权 ClusterRole "view":<br> kubectl create rolebinding myapp-view-binding --clusterrole=view --serviceaccount=came:myapp --namespace=came<br>
# 创建ClusterRoleBinding: 在集群范围内进行授权(为Subject绑定ClusterRole)<br>1、在集群范围授权root ClusterRole "cluster-admin"<br>kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --user=root<br><br>2、为集群范围用户 ”system:kube-proxy“ 授权 ClusterRole “system:node:proxier”<br>kubectl create clusterrolebinding kube-proxy-bining --clusterrole=system:node-proxier --user=system:kube-proxy<br>
移除多余的权限
1、测试允许RBAC规则,显示将要执行的更改(也就是不会真正改变)<br>kubectl auth reconcile -f cluster-role-demo.yaml --dry-run=client<br>
2、应用输入配置中的内容,保留任何额外权限Role和任何额外主体Bindings<br>kubectl auth reconcile -f cluster-role-demo.yaml<br>
3、删除任何额外权限和任何额外主体<br>kubectl auth reconcile -f cluster-role-demo.yaml --remove-extra-subjects --remove-extra-permissions<br>
聚合ClusterRole
1、k8s支持将多个ClusterRole聚合成一个新的ClusterRole,好处是可在希望将多个ClusterRole的授权规则进行合并使用时,简化管理员的手工配置工作,完成对希望默认的ClusterRole的扩展。(如CRD或者Aggergated API Server提供的资源授权认证)
# 例子:创建个包含以上标签的ClusterRole,则系统会自动为聚合ClusterRole设置rules<br>https://github.com/tzh666/k8s-yaml/blob/main/rbac/cluster-role-add.yaml<br><br># 验证结果. 发现权限已经绑定上去,那么到时候使用就可以只使用monitoring即可<br>kubectl get clusterrole monitoring -oyaml<br>
# 例子:使用聚合规则对系统默认的ClusterRole进行扩展(内置view、admin、edit等)<br># 查看 view的标签<br>[root@master01 k8s-yaml]# kubectl get clusterrole view -oyaml | grep -A 1 match<br> - matchLabels:<br> rbac.authorization.k8s.io/aggregate-to-view: "true"<br><br># 假设用户希望自定义资源对象 crontabs,jobs 设置只读权限,并且加入一个内置的名为view的ClusterRole中,则基于view设置的聚合规则。用户自需要新建一个Role,打上view的标签,即可将其相关授权规则加入到view的权限列表中<br>https://github.com/tzh666/k8s-yaml/blob/main/rbac/cluster-role-add-view.yaml<br>
常见的授权规则示例
暂时省略
系统默认的授权规则(ClusterRole和ClusterRoleBinding)
1、API Server会自动创建一组系统默认的ClusterRole和ClusterRoleBinding对象,其中很多都是以 "system:" 为前缀的,表明这些资源直接被k8s master直接管理,对这些对象改动可能会造成集群故障<br><br>2、授权的自动恢复规则(Auto-reconciliation)功能从k8s1.6版本开始引入。每次启动集群,API Server都会更新默契的集群角色的缺失权限,也会更新默认的角色绑定中缺失的主体,这样就方式一些破坏性的集群角色的修改,也保证了集群升级的时候相关内容能即使更新。<br><br>3、该权限开启RBAC认证后自动开启,可设置单独关闭 annotation中设置即可
预防权限提升和授权初始化
1、创建、更新Role或ClusterRole的限制
# 用户要对角色或者集群角色进行创建或者更新操作,<b>必须满足以下至少一个条件<br></b><ol><li><span style="font-size: inherit; font-weight: bold;">用户已拥有Role中包含的所有权限,且该角色的生效范围一致,如果是集群角色,则是集群范围;如果是普通角色则可是用一个ns或者整个集群</span></li><li><span style="font-size: inherit; font-weight: bold;">用户被显示授权针对Role或ClusterRole的资源的提权(Escalate)操作权限</span></li></ol><br>
# 例子:用户user-1没有列出集群中所有Secret资源的权限,就不能创建具有这一权限的集群角色。要让一个用户能够创建或者更新角色,则需要:<br>1、为其授予一个允许创建或者更新的Role或者ClusterRole资源对象的角色<br>2、为其授予创建更新或者创建角色的权限,有隐式和显式两种方式<br> - 隐:为用户授予这些权限<br> - 显:为用户显示授予rbac.authorization.k8s.io API Group 中Role或者ClusterRole的提权(Escalate)操作权限
2、创建、更新RoleBinding或ClusterRoleBinding的限制
类似于上面的授权
对ServiceAccount的授权管理
# Kubernetes Pod/容器的安全管控<br>Security Context<br>
背景:运行一个容器的时候,可能需要使用 sysctl 命令来修改内核参数,比如 net、vm、kernel 等参数,但是 systcl 需要容器拥有超级权限,才可以使用,在 Docker 容器启动的时候我们可以加上 --privileged 参数来使用特权模式。那么在 Kubernetes 中应该如何来使用呢?
这个时候我们就需要使用到 <b>Kubernetes 中的 Security Context,也就是常说的安全上下文</b>,<font color="#d32f2f">主要是来限制容器非法操作宿主节点的系统级别的内容,使得节点的系统或者节点上其他容器组受到影响</font>
Kubernetes 提供了三种配置安全上下文级别的方法
Container-level Security Context:仅应用到指定的容器
Pod-level Security Context:应用到 Pod 内所有容器以及 Volume
Pod Security Policies(PSP):应用到集群内部所有 Pod 以及 Volume
设置 Security Context的方式
访问权限控制:根据用户 ID(UID)和组 ID(GID)来限制对资源(比如:文件)的访问权限
Security Enhanced Linux (SELinux):为对象分配 SELinux 标签
以 privileged(特权)模式运行
Linux Capabilities:给某个特定的进程超级权限,而不用给 root 用户所有的 privileged 权限
AppArmor:使用程序文件来限制单个程序的权限
Seccomp:过滤容器中进程的系统调用(system call)
AllowPrivilegeEscalation(允许特权扩大):此项配置是一个布尔值,定义了一个进程是否可以比其父进程获得更多的特权,直接效果是,容器的进程上是否被设置 no_new_privs 标记。当出现如下情况时,AllowPrivilegeEscalation 的值始终为 true:
容器以 privileged 模式运行
容器拥有 CAP_SYS_ADMIN 的 Linux Capability
操作实例
为 Pod 设置 Security Context<br># 在线配置文件<br>https://github.com/tzh666/k8s-yaml/blob/main/security-context/Security-Context.yaml<br>
runAsUser:指定了该 Pod 中所有容器的进程都以 UID 1000 的身份运行,runAsGroup 字段指定了该 Pod 中所有容器的进程都以 GID 3000 的身份运行
fsGroup 字段指定了该 Pod 的 fsGroup 为 2000,数据卷 (对应挂载点 /pod/demo 的数据卷为 sec-ctx-demo) 的所有者以及在该数据卷下创建的任何文件,其 GID 都为 2000
runAsGroup 字段指定了该 Pod 中所有容器的进程都以 GID 3000 的身份运行
如果省略该字段,容器进程的 GID 为 root(0)
容器中创建的文件,其所有者为 userID 1000,groupID 3000
常用的一些 securityContext 字段设置内容介绍
为容器设置 Security Context<br># 在线配置文件<br>https://github.com/tzh666/k8s-yaml/blob/main/security-context/security-context-pod-demo-2.yaml<br>
注意:<br>1、当该字段的配置与 Pod 级别的 securityContext 配置相冲突时,容器级别的配置将覆盖 Pod 级别的配置<br>2、容器级别的 securityContext 不影响 Pod 中的数据卷
设置 Linux Capabilities<br># 在线配置文件
背景:<br>1、使用 docker run 的时候可以通过 --cap-add 和 --cap-drop 命令来给容器添加 Linux Capabilities<br>2、
Linux Capabilities 是什么?
1、从内核 2.2 开始,Linux 将传统上与超级用户 root 关联的特权划分为不同的单元,称为 capabilites<br>2、Capabilites 每个单元都可以独立启用和禁用<br>3、在执行特权操作时,如果进程的有效身份不是 root,就去检查是否具有该特权操作所对应的 capabilites,并以此决定是否可以进行该特权操作<br><br># 比如如果我们要设置系统时间,就得具有 CAP_SYS_TIME 这个 capabilites<br>官网列表:https://man7.org/linux/man-pages/man7/capabilities.7.html
Linux如何使用 Capabilities
1、可以通过 getcap 和 setcap 两条命令来分别查看和设置程序文件的 capabilities 属性)
getcap /bin/ping
# 去掉权限ping命令就没有权限了<br>sudo setcap cap_net_admin,cap_net_raw-p /bin/ping<br>
# 重新加权限<br>sudo setcap cap_net_admin,cap_net_raw+p /bin/ping
命令中的 p 表示 Permitted 集合,<b><font color="#d32f2f">+ 号表示把指定的capabilities 添加到这些集合中,- 号表示从集合中移除</font></b>
对于可执行文件的属性中有三个集合来保存三类 capabilities
Permitted:在进程执行时,Permitted 集合中的 capabilites 自动被加入到进程的 Permitted 集合中
Inheritable:Inheritable 集合中的 capabilites 会与进程的 Inheritable 集合执行与操作,以确定进程在执行 execve 函数后哪些 capabilites 被继承
Effective:Effective 只是一个 bit。如果设置为开启,那么在执行 execve 函数后,Permitted 集合中新增的 capabilities 会自动出现在进程的 Effective 集合中
对于进程中有五种 capabilities 集合类型,相比文件的 capabilites,进程的 capabilities 多了两个集合,分别是 Bounding 和 Ambient
cat /proc/7029/status | grep 'Cap' #7029为PID<br>CapInh: 0000000000000000<br>CapPrm: 0000000000000000<br>CapEff: 0000000000000000<br>CapBnd: 0000001fffffffff<br>CapAmb: 0000000000000000<br>
可以使用 capsh 命令把它们转义为可读的格式<br>capsh --decode=0000001fffffffff<br>
docker中使用Capabilities
1、运行容器的时候可以通过指定 --privileded 参数来开启容器的超级权限,这个参数一定要慎用,因为他会获取系统 root 用户所有能力赋值给容器,并且会扫描宿主机的所有设备文件挂载到容器内部,所以是非常危险的操作
Docker 容器本质上就是一个进程,所以理论上容器就会和进程一样会有一些默认的开放权限,默认情况下 Docker 会删除必须的 capabilities 之外的所有 capabilities,因为在容器中我们经常会以 root 用户来运行,使用 capabilities 现在后,容器中的使用的 root 用户权限就比我们平时在宿主机上使用的 root 用户权限要少很多了,这样即使出现了安全漏洞,也很难破坏或者获取宿主机的 root 权限,所以 Docker 支持 Capabilities 对于容器的安全性来说是非常有必要的
--cap-add和--cap-drop 这两参数都支持ALL值,比如如果你想让某个容器拥有除了MKNOD之外的所有内核权限,那么可以执行下面的命令<br>sudo docker run --cap-add=ALL --cap-drop=MKNOD ...<br>
Kubernetes中使用Capabilities
1、Pod 定义的 spec.containers.securityContext.capabilities中即可,也可以进行 add 和 drop 配置<br><br>https://github.com/tzh666/k8s-yaml/blob/main/security-context/cpb-demo.yaml<br>
原理:在 Kubernetes 中通过 sercurityContext.capabilities 进行配置容器的 Capabilities,当然最终还是通过 Docker 的 libcontainer 去借助 Linux kernel capabilities 实现的权限管理
为容器设置 SELinux 标签
Pod 或容器定义的 securityContext 中 seLinuxOptions 字段是一个 SELinuxOptions 对象,该字段可用于为容器指定 SELinux 标签
securityContext:<br> seLinuxOptions:<br> level: "s0:c123,c456"
k8s存储
基本概念
1、Volume是于Pod绑定的(独立于容器,是Pod级别的),与Pod具有相同的生命周期<br>2、Volume的内容可以理解为目录或者文件,当容器需要某个Volume时,仅需要设置volumeMounts将一个或者多个Volume挂载为容器中的目录或者文件,即可访问Volume中的数据
Volume类型
k8s中的资源对象
cm
secret
DownwardAPI: Pod或者container的元数据信息
ServiceAccountToken:SA中的token数据
Projected Volume:一种特殊的存储卷类型,<b><font color="#d32f2f">用于将一个或者多个上述资源对象一次性挂载到容器内的同一个目录下</font></b>
k8s管理的宿主机本地存储类型
EmptyDir:临时存储
HostPath:宿主机目录
持久化存储PV和网络共享存储类型
CephFS:一种开源共享网络存储系统
Cinder:一种开源共享网络存储系统
CSI:容器存储接口(由存储提供商提供驱动程序和存储管理程序)
FC(Fiber Channel):光纤存储设备
Flex Volume:一种基于插件式驱动的存储
Flocker:一种开源共享网络存储系统
RBD:ceph块存储
GFS
iSCSI
Local
NFS
PVC
等等等
存储实战
将资源对象映射为存储卷
cm
secret
downward API
https://github.com/tzh666/k8s-yaml/blob/main/downward-API/dapi-envvars-pod.yaml
Projected Volume
Projected Volume:一种特殊的存储卷类型,<b><font color="#d32f2f">用于将一个或者多个上述资源对象一次性挂载到容器内的同一个目录下<br></font></b>
验证: kubectl exec -it volume-test -- ls /projected-volume 有1个文件和2个文件夹,对应配置文件<br><b><font color="#d32f2f"><br></font></b># 挂载cm-secret-downwardAPI<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/projected_volume/projected-volume.yaml<br>
SA token
# 挂载SA token
报错解决:<br>https://blog.csdn.net/weixin_43669903/article/details/111709545<br>
<b>/etc/kubernetes/manifests/kube-apiserver.yaml里添加,重启kube-apiserver<br></b>- --service-account-signing-key-file=/etc/kubernetes/pki/sa.key<br>- --service-account-key-file=/etc/kubernetes/pki/sa.pub<br>- --service-account-issuer=api<br>- --service-account-api-audiences=api,vault,factors<br>
# 配置文件<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/projected_volume/to-sa-token.yaml<br><br># 验证结果<br>[root@master01 projected_volume]# kubectl logs -f sa-token-test<br>token<br>
Node本地存储
EmptyDir
<font color="#d32f2f">与Pod同生命周期的Node</font><b>临时存储</b>
应用场景
基于磁盘进行合并序列,操作时需要的暂时空间
长时间计算任务的中间检查点文件
为某个Web服务提供的临时网站内容
<b>配置简单:<br>spec.volumes:<br>- name: test<br> emptyDir: {}</b>
HostPath
Node所在宿主机目录
应用场景
容器应用的关键数据需要被持久化到宿主机上
需要使用Docker中的某些内部数据,可以将/var/lib/doker 目录挂载到容器内
监控系统,例如才Aviso需要采集宿主机 /sys 目录下的内容
Pod启动需要依赖宿主机上的某个目录或者文件就绪的场景
使用注意
1、多个Pod路径尽量不用相同,否则配置文件会混乱
2、如果设置了基于存储资源的调用,<b><font color="#b71c1c">HostPath目录下的磁盘空间将无法纳入Node可用的资源范围内</font></b>,可能会出现与预期不同的调度结果
3、Host不会被Pod自动删,需要人工手动delete
主要参数HostPath说明<br>
path:设置宿主机的目录或者文件
type:<br>
空:默认值,为向后兼容的设置,系统挂载时不做校验
DirectoryOrCreate: path指定的必须是目录,如果目录不存在将自动创建,权限0755,与kubelet具有相同的owner、group
Directory:path指定的目录必须存在,否则挂载失败
FileorCreate:path指定的必须是文件,如果目录不存在将自动创建,权限0644,与kubelet具有相同的owner、group
Socket:path指定的UNIX socket必须存在,否则挂载失败<br>
CharDevice:支持的字符设备必须存在,否则挂载失败
BlockDevice:块设备必须存在,否则挂载失败
实例:<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/node-volume/hostpath-demo.yaml<br>
原理概念
基于持久化的PV,PVC,SC
PV
概念:<br>1、PV(持久卷)是对资源存储的抽象,将存储定义为一种容器应用可以使用的资源<br>2、PV由管理员创建和配置,他与存储提供商的具体实现直接相关,例如GFS、iSCIC、RBD、AWS的共享存储,通过插件的机制进行管理,供应用访问和使用<br>3、<b>除了EmptyDir类型的存储卷,PV的生命周期独立于使用它的Pod</b>
PVC
概念:<br>1、PVC是用户对存储资源的一个申请。就像Pod消耗Node的资源一样,PVC消耗PV的资源<br>2、PVC可以申请存储空间的大小(size)和访问模式
SC
概念:<br>1、通过StorageCless的定义,管理员可以将存储资源定义为某种类别,正如存储设备对自身的配置描述(Profile),例如快、慢速存储、有、无数据冗余等<br>2、用户根据SC的描述就可以直观的得知各种存储资源的特性,根据应用对存储资源的需求去申请资源
CSI
概念:<br>1、目标是建立k8s和外部存储系统之间建立一套标准的存储管理接口,具体的存储驱动程序由存储提供商在k8s之外提供,并通过该标准接口为容器提供存储服务,类似于CRI、CNI,目的是将k8s代码与存储相关代码解耦
PV和PVC的工作原理
PV和PVC的生命周期
1、资源供应:创建PV、PVC
2、资源绑定:PVC绑定上PV
3、资源使用:Pod使用PVC
4、资源回收:使用完删除PVC,回收PV资源
PV和PVC的生命周期详解
1、资源供应
静态模式
概念<br>1、集群管理员手动创建PV
动态模式
概念:<br>1、无需管理员手动创建PV,而是通过SC的设置对后端存储进行描述,标记存储的类型和特性<br>2、<b>用户通过创建PVC对存储类型进行申请,系统将自动完成PV的创建及与PVC的绑定<br></b>3、如果PVC声明的Class为空" ",则说明PVC不使用动态存储模式<br>4、通过kube-apiserver开启准入控制器DefaultStorageClass,可以为用户创建一个PVC默认的存储类SC
2、资源绑定
# 静态模式<br>1、当用户定义好PVC后,系统将根据PVC对存储资源的请求(存储空间跟访问模式),在已存在的PV中选择一个满足PVC要求的PV,一旦找到,就将该PV与用户定义的PVC绑定,用户的应用就可以使用这个PVC了。<br>2、如果没有满足PVC要求的PV,PVC则处于Pending的状态,直到有符合其要求的PV被创建<br><br># 动态模式<br>3、系统在为PVC找到合适的StorageClass后,将自动创建一个PV并完成与PVC的绑定<br>
# 其他说明<br>1、PV与PVC是一对一的关系,一旦绑定上PV就被PVC独占<br>2、PVC申请的资源空间不能比PV拥有的空间大
3、资源使用
# 使用说明<br>1、Pod在使用存储资源时,需要在volume的定义中引用PVC类型的存储卷,将PVC挂载到容器内部路径使用<br>2、一个PVC可以同时被多个Pod使用,这种情况需要程序处理好多个进程同时访问同一个存储的问题(不建议这样使用)
<b># 存储对象保护机制说明:<br></b>1、存储资源(PV、PVC)相对于容器应用(Pod)都是独立的管理的资源,都可以单独删除<br>2、在做删除操作的时候,系统会检查存储资源当前是否正在被使用,如果被使用,则对相关资源对象的删除操作将被推迟,直到没被使用才会执行删除操作,这样可以确保数据丢失
4、资源回收
# 资源回收说明<br>1、用户在使用完存储资源后,可以删除PVC。与该PVC绑定的PV将被标记为 "已释放(Released)"。但这个被标记为 "已释放"的PV还不能立刻与其他的PVC绑定<br>2、因为通过之前绑定的PVC写入的数据可能还在存储设备PV上,只能删除PV的数据,这个PV才能再次使用
PV资源回收策略
Retain(保留数据)<br>
1、表示删除PVC后吗,与之前绑定的PV不会被删除,状态标记为以释放(released)<br>2、PV中的数据还在,不能跟新的PVC使用,需要清理数据后才能继续使用<br>
# PV清理步骤<br>1、删除PV对象<br>2、清理PV后端存储中的数据<br>3、再次创建一个PV对象
Delete(删除数据)
1、表示删除PVC后<b>自动删除PV资源对象和后端相关存储资产,</b><font color="#d32f2f">但是并不是所有的存储供应商都支持Delete策略<br></font>2、动态存储创建的PV默认回收策略是Delete策略
Recycle(清除PV的数据)[弃用]
1、目前只有FNS、HostPath类型的Volume支持Recycle策略,其允许机制为rm -rf /volume/*, 删除目录下的全部文件,使得PV可以被新的PVC使用
PVC资源扩容
支持PVC扩容的存储类型
AWSSElasticBlockStore
AzureDisk
AzureFile
GFS
CSI
RBD
扩容操作:
1、在StorageClass在设置alllowVolumeExpansion=true
2、修改PVC定义,将resources.requests.storage设置一个更大值即可
PV和PVC详解
PV详解
容量类型
1、只能通过storage=xxxGi来设置
存储卷模式<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/pv1.yaml<br>
持久化存储PV和网络共享存储类型
CephFS:一种开源共享网络存储系统
Cinder:一种开源共享网络存储系统
CSI:容器存储接口(由存储提供商提供驱动程序和存储管理程序)
FC(Fiber Channel):光纤存储设备
Flex Volume:一种基于插件式驱动的存储
Flocker:一种开源共享网络存储系统
GFS
iSCSI
Local
NFS
PVC
RBD:ceph块存储
访问模式
ReadWriterOnce(RWO):读写权限,<b>并且只能被一个Node挂载</b>
ReadOnlyMany(ROX):只读权限,允许被多个Node挂载
ReadWriteMant(RWX):读写权限,允许被多个Node挂载
资源回收策略
Retain(保留数据)<br>
1、表示删除PVC后吗,与之前绑定的PV不会被删除,状态标记为以释放(released)<br>2、PV中的数据还在,不能跟新的PVC使用,需要清理数据后才能继续使用<br>
# PV清理步骤<br>1、删除PV对象<br>2、清理PV后端存储中的数据<br>3、再次创建一个PV对象
Delete(删除数据)
1、表示删除PVC后<b>自动删除PV资源对象和后端相关存储资产,</b><font color="#d32f2f">但是并不是所有的存储供应商都支持Delete策略<br></font>2、动态存储创建的PV默认回收策略是Delete策略
Recycle(清除PV的数据)[弃用]
1、目前只有FNS、HostPath类型的Volume支持Recycle策略,其允许机制为rm -rf /volume/*, 删除目录下的全部文件,使得PV可以被新的PVC使用
挂载选项
mountOptions
后面补充
节点亲和性
1、PV可以设置节点亲和性来限制只能通过某个Node访问Volume,在PV定义中的nodeAffinity字段中进行设置<br>2、公有云提供的存储卷都有公有云自动完成节点亲和性设置,无需用户手工设置<br>3、对于Local类型的PV需要手工设置
PV生命周期
Available:可用状态,还未与某个PVC绑定
Bound:已与某个PVC绑定
Released:与之绑定的PVC已被删除,但未完成资源回收,不能被其他的PVC使用
Failed:自动资源回收失败
PVC详解
SC详解
概念:<br>1、StorageClass作为对存储资源抽象的定义,对用户设置的PVC申请屏蔽后端存储的细节,<b>一方面减少了用户对存储资源细节的关注,另一方面减轻了用户对PV的创建和绑定,实现动态的资源供应。<br><br></b>注意:<br>1、SC一旦被创建,就无法在线更改,只能先删除后修改,不能edit
StorageClass 实战<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/SC/demo-sc.yaml<br>
参数详解
provisioner(存储提供者)
1、描述存储资源的提供者,用于提供具体的PV资源,也可以将其看作后端存储驱动<br>2、目前k8s内置支持的Provisioner有:******<br>3、k8s内置的Provisioner的命名都以 "kubernetes.io"开头,用户也可以使用自定义的后端存储提供者,例如NFS类型,k8s中没有提供内部的Provisioner,但是可以使用外部的Provisioner<br>
reclaimPolicy(资源回收策略)
Delete(默认)
Retain
allowVolumeExpansion(是否允许扩容)<br>
1、设置为true的时候。将允许用户通过编辑PVC的存储空间自动将完成PV的扩容。<b>要注意的只支持扩容,不支持缩容</b><br>
mountOptions(挂载选项)
1、通过StrongerClass资源对象的mountOptions字段,<b>系统将为动态创建的PV设置挂载选项</b>。<br>2、并不是所有PV类型都支持挂载选项,如果不支持自动挂载的PV设置了该选项,PV将会创建失败<br>3、另外,系统不会对挂载选项进行验证,如果设置了错误的选项,则容器在挂载存储时将直接失败
volumeBindingMode(存储绑定模式)
Immediate(默认)
1、存储绑定模式默认值(Immediate),表示当一个PVC创建出来时,就动态的创建PV且进行PVC跟PV的绑定操作<br>2、注意点是:对拓扑受限(Topology-limited)或 无法从全部Node访问的后端存储,将在不了解Pod调度需求的情况下完成PV的绑定操作,这可能导致某些Pod无法完成调度
WaitForFirstConsumer(延时绑定)
1、表示只有当Pod使用PVC时,PVC才会绑定PV<br>2、系统会根据Pod的调度需求,在Pod所在的Node上创建PV
调度限制demo<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/SC/demo-sc-wait.yaml<br>
parameters(存储参数)
存储参数最多512个详情得看具体后端的存储供应商
默认的StrongerClass
1、管理员可以为不同的存储需求的PVC创建相应的SC来提供动态的PV供应,同时在集群级别设置一个默认的SC,为那些未指定SC的PVC使用<br>2、开始方式在kube-apiserver中--enable-admission-plugins中开启,--enable-admission-plugins=...,DefaultStorageClass(从1.10默认开启)<br>3、然后在StrongerClass中的定义设置一个annotation:<b>storageclass.beta.kubernetes.io/is-default-class="true", </b>创建后get sc 即可看到默认的sc<br><b>4、后续未指定SC的PVC,在创建到时候系统会自动为其设置集群中默认的SC</b>
Local实战
PV和PVC实战
hostPath实战
概念:<br>1、使用 hostPath 的话我们就需要将 Pod 固定在某个节点上,但是会降低容错性【相对安全性低】<br>
1、使用 hostPath 的话我们就需要将 Pod 固定在某个节点上node01上<br>2、/data/k8s/test/hostpath 的目录,然后在该目录中创建一个 index.html 的文件
# 在线配置文件,搭配 nodeSelector将Pod固定节点,然后Pod在漂移的时候就不会读取不到文件<br>https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/pv-hostpath.yaml<br><br>验证<br>[root@master01 PV]# curl 10.244.1.63 (Pod IP)<br>Hello from Kubernetes hostpath storage<br>
Local PV实战
概念:<br>1、Local PV 实现的功能就非常类似于 hostPath 加上 nodeAffinity<br>2、一般来说 Local PV 对应的存储介质是一块额外挂载在宿主机的磁盘或者块设备,我们可以认为就是“一个 PV 一块盘”<br>3、Local PV 和普通的 PV 有一个很大的不同在于 Local PV 可以保证 Pod 始终能够被正确地调度到它所请求的 Local PV 所在的节点上面
实战1:<br>1、给宿主机挂载并格式化一个可用的磁盘,我们这里就暂时将 node02 节点上的 /data/k8s/localpv 这个目录看成是挂载的一个独立的磁盘<br><br>https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/pv-local.yaml<br>
# 实战2:手动编写sc、pvc、pv实现延迟绑定【公司就是用这种】<br><br>https://github.com/tzh666/k8s-yaml/blob/main/volume/PVC/local-storageclass-sys.yaml<br><br>https://github.com/tzh666/k8s-yaml/blob/main/volume/PVC/local-storageclass.yaml<br>
删除 PV 时需要按如下流程执行操作<br><b>如果不按照这个流程的话,这个 PV 的删除就会失败</b><br>
<ol><li>删除使用这个 PV 的 Pod</li><li>从宿主机移除本地磁盘</li><li>删除 PVC</li><li>删除 PV</li></ol>
OpenEBS存储
https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/OpenEBS%E5%AD%98%E5%82%A8.pdf
Ceph存储
https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/Ceph%E5%AD%98%E5%82%A8.pdf
GFS存储
https://github.com/tzh666/k8s-yaml/blob/main/volume/GFS/GFS-to-k8s.md
StorageClass 动态存储实战
https://www.yuque.com/linuxkaifa/k8s-sc
存储原理
https://github.com/tzh666/k8s-yaml/blob/main/volume/PV/%E5%AD%98%E5%82%A8%E5%8E%9F%E7%90%86.pdf
k8s网络
Kubernetes网络模型
k8s网络模式设计的一个基础原则:<br>
1、每一个Pod都拥有独立的IP地址,并假定所有Pod都在一个可以直连的、扁平的网络空间中<br>
2、不管它们在不在一个Node上,都要求它们可以直接访问对方的IP
优点:用户不需要额外考虑如何建立Pod之间的连接,也不需要考虑如何将容器端口映射到主机端口的问题
IP-per-Pod模型:
在k8s世界里,IP是以Pod为单位进行分配的。一个Pod内部所有的容器共享一个网络栈(相当于一个网络名称空间,它们的IP、网络设备、配置等都是共享的)。按照这个网络原则抽象出来为每一个Pod设置一个IP地址模型也被称作为IP-per-Pod模型
k8s对网络有什么前提跟要求?
1、所有Pod都可以在不用NAT的方式下同别的Pod通信
2、在所有节点运行代理程序(例如kubelet或者操作系统的守护进程)都可以在不用NAT的方式下同所有Pod通信,反之亦然
3、以hostnetwork 模式运行的Pod都可以在不用NAT的方式同别的Pod通信
Docker网络基础
网络命名空间
网络命名空间是什么?
1、为了支持网络协议栈的多个实例,Linux在网络栈中引用了网络命名空间,这些独立的协议栈被隔离到不同的命名空间中<br>2、这些处于不同命名空间中的网络栈是完全隔离的,彼此之间无法通信<br>3、通过对网络资源的隔离,就能在一个宿主机上虚拟多个不同的网络环境<br><b>4、Docker正是利用了网络的名称空间特性,实现了不同容器之间的网络隔离</b>
网络命名空间的实现
为了支持独立的协议栈,相关的这些全局变量都必须被修改为协议栈私有。最好的办法是让这些全局变量变成一个net namespace变量的成员,然后为协议栈的函数调用加入一个Namespace参数。这就是Linux实现网络命名空间的核心
为了保证已经开发的程序以及内核代码的兼容性,内核代码隐式地使用了名称空间中的变量。如果你的程序没有对命名空间有特殊的需求,就不需要编写额外的代码,网络命名空间对应用程序而言是透明的
网络命名空间的操作(需要由root用户运行)
创建一个命名空间:ip netns add <name>
在命名空间中运行命令:ip netns exec <name> <command>
进入命令空间中,运行各种命令:ip netns exec <name> bash
查看设备是否可以移动到别的命名空间:ethtool -k br0 <br><br>netns-loacl: on ,值是on说明可以转移,否则不行
Veth设备对
什么是设备对?
1、引用设备对是为了在不同的网络命名空间之间通信,利用它可以直接将两个网络命名空间连接起来。<br>2、由于要连接两个网络命名空间,所以Veth设备对都是成对出现的<br><br>3、类似于一根网线的两个水晶头,连接起来就可以上网的存在
设备对操作命令
创建设备对,veth0、veth1:<br>ip link add veth0 type veth peer name veth1<br>
查看设备对信息:ip link show
添加一个命名空间,然后将一个设备对丢进去<br><br># 添加ns<br>ip netns add tzh<br><br># 移动设备对<br>ip link set veth1 netns tzh<br>
# 再次查看设备对,此时在当前命名空间下查看不到veth1<br>ip link show | grep veth1<br><br># 然后在tzh这个命名空间中查看,可以看到。这符合预期<br>ip netns exec tzh ip link show | grep veth1<br>
分配网络,然后测试通信效果<br>ip netns exec tzh ip addr add 10.1.1.1/24 dev veth1<br><br>ip addr add 10.1.1.2/24 dev veth0<br><br># 启动设备对<br>ip netns exec tzh ip link set dev veth1 up<br>ip link set dev veth0 up<br><br># 测试通信<br>[root@master01 ~]# ip netns exec tzh ping 10.1.1.2<br>PING 10.1.1.2 (10.1.1.2) 56(84) bytes of data.<br>64 bytes from 10.1.1.2: icmp_seq=1 ttl=64 time=0.163 ms<br>64 bytes from 10.1.1.2: icmp_seq=2 ttl=64 time=0.074 ms<br><br><br>[root@master01 ~]# ping 10.1.1.1<br>PING 10.1.1.1 (10.1.1.1) 56(84) bytes of data.<br>64 bytes from 10.1.1.1: icmp_seq=1 ttl=64 time=0.168 ms<br>64 bytes from 10.1.1.1: icmp_seq=2 ttl=64 time=0.077 ms<br>
经过上面的模拟实验,我们知道了设备对的原理跟用法。实际上在Dockers网络中,Veth设备对也是连接容器与宿主机的主要网络设备,离开它是不行的
设备对如何查看对端
# 一旦将一个设备对放到另一个命名空间,在原命名空间中就看不到它了
<b>1、在命名空间tzh中查看Veth设备对端接口在设备列表中的序列号<br></b>[root@master01 ~]# ip netns exec tzh ethtool -S veth1<br>NIC statistics:<br> peer_ifindex: 11<br>
<b>2、在原有的命名空间中查看序列号为11的即可<br></b>ip link | grep 11<br>
删除设备对
# 删除<br>ip link del veth0<br><br># 删一个,另一个也不见了<br>ip netns exec tzh ip link show veth1<br>
网桥
什么是网桥?<b>br0,把2个网卡做桥接呗</b>
1、网桥是一个虚拟的二层网络,把若干个网络接口 "连接"起来,以使得网络接口之间的报文能够相互转发<br>2、网桥能够解析收发的报文,读取目标MAC地址信息,将其与自己的记录MAC表结合,来决策报文转发目标网络接口
Linux网桥的实现
1、Linux内核是通过一个虚拟网桥设备(Net Device)来实现桥接的<br>2、这个设备可以绑定若干个以太网接口设备,从而将它们桥接起来<br>3、因为桥接是在数据链路层实现的,上层不需要关心桥接的细节,所以协议栈上层需要发生的报文被发送到br0即可,剩下的交给br0的代码判断发生给谁就行,反之如此
网桥操作命令
新增一个网桥(当然也可以写在配置文件中):<br>brctl addr name<br>
桥接网口:<br>brctl addif name 网卡名
iptables 和 Netfilter
这2玩意干啥的?
1、Netfilter 负责在内核中执行各种挂接的规则,运行在内核模式中;<br>2、iptables负责协助和维护内核中Netfilter的各种规则表,是在用户模式下运行的进程<br><br>作用:<br>两者相互配合来实现整个Linux网络协议栈中灵活的数据包处理机制
路由
Docker的网络实现
host模式: 使用--net=host指定
container模式:使用--net=container:NAM_or_ID指定
node模式:使用--net=node指定
<b>bridge模式:使用--net=bridge指定,<br>为Docker默认设置,在k8s管理模式下通常只会使用这个模式</b><br>
bridge模式简介
1、在bridge模式下,Docker Daemon首次启动会自动创建一个虚拟网桥,默认是名称是docker0,然后按照RPC1918的模型在私有网络空间中给这个网桥分配一个子网
2、<b>针对由docker创建的每一个容器,都会创建一个虚拟以太网设备(Veth设备对)</b>,<font color="#4caf50">其中一端关联到网桥上</font>,<font color="#d32f2f">另一端使用Linux的网络命名空间技术映射到容器内的eth0设备</font>,<b>然后在网桥的地址段内给eth0接口分配一个IP地址</b>
Kubernetes的网络实现
容器到容器之间的通信
1、同一个Pod内的容器(Pod内的容器是不会夸主机的)共享同一个网络命名空间,共享一个Linux协议栈。(可以理解他们在同一个机器上,使用localhost地址可以访问彼此的端口)
架构图:<br>https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/k8s%E4%B9%8BPod%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E6%9E%B6%E6%9E%84%E5%9B%BE.jpg<br>
如图所示,容器1可以通过localhost: port来访问容器2的服务,反之亦可
抽象的Pod到Pod之间的通信
同一个Node上的Pod之间的通信
架构图:<br>https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/k8s%E4%B9%8BPod%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E6%9E%B6%E6%9E%84%E5%9B%BE.jpg<br>
从架构图可直观的看到:<br>1、Pod1跟Pod2都是通过Veth连接到同一个docker0网桥的,它们的IP地址IP1、IP2都是同docker0的网段动态获取的,和网桥IP3属于同一个网段<br>2、在Pod1、Pod2的Linux协议栈上,默认路由都是docker0的地址,也就是说所有非本地(夸Pod的通讯)地址的网络通信,都要通过docker0网桥上,由docker0网桥之间中转<br><br><b>3、由于IP1、IP2、IP3都关联在docker0网桥上,地址段相同,所以它们之间是可以之间通信的</b>
不同Node上的Pod执行的通信
架构图:<br>https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/k8s%E4%B9%8BPod%E7%BD%91%E7%BB%9C%E9%80%9A%E4%BF%A1%E6%9E%B6%E6%9E%84%E5%9B%BE.jpg<br>
从架构图中可直观看到:<br>1、Pod1的容器通过设备(IP1)对可以访问到Docker网桥,也就能访问到物理网卡IP3<br>2、IP3再连接到另一个Node2的IP4,再回到IP2
<b>想要支持不用Node上Pod之间的通信,需要满足的2个条件:<br></b>1、在k8s中对Pod的IP分配进行规划,不能有冲突<br>2、将Pod的IP和所在的Node的IP关联起来,通过这个关联的让Pod可以相互访问
方案:<br>解决条件1:例如使用网络增强开源插件Flannel<br>解决条件2:。。。。
Pod到Service之间的通信
集群内部与外部组件之间的通信
CNI网络模型(Container Network Interface)
网络插件
Flannel
什么是Flannel
1、Flannel 由coreOS开发,<b>用于解决docker集群跨主机通讯的覆盖网络(overlay network)</b><br>
2、它的主要思路是:<b>预先留出一个网段,每个主机使用其中一部分</b>,<b>然后每个容器被分配不同的ip;让所有的容器认为大家在同一个直连的网络</b>,底层通过<b>UDP/VxLAN/Host-GW</b>等进行报文的封装和转发
Flannel实现原理
1、集群中的不同节点上,创建的Pod具有全集群唯一的虚拟IP地址
2、建立一个覆盖网络(overlay network),通过这个覆盖网络,将数据包原封不动的传递到目标容器
3、覆盖网络通过将一个分组封装在另一个分组内,来将网络服务与底层基础设施分离。在将封装的数据包转发到端点后,将其解封装。
4、创建一个新的虚拟网卡flannel0接收docker网桥的数据,通过维护路由表,对接收到的数据进行封包和转发(vxlan)
5、etcd保证了所有node上flanned所看到的配置是一致的。同时每个node上的flanned监听etcd上的数据变化,实时感知集群中node的变化。
Flanneld 作用
Flanneld 收到 EventAdded 事件后,从 etcd 将其他主机上报的各种信息,在本机上进行配置,主要分下列三种信息:<br><br>ARP:IP和MAC的对应关系,三层转发<br>FDB:MAC+VLAN和PORT的对应关系,二层转发,即使两个设备不在同一网段或者没配置IP,只要两者之间的链路层是连通的,就可以通过FDB表进行数据转发。它作用就在于告诉设备从某个端口出去就可以到某个目的MAC<br>Routing Table:通往目标地址的封包,通过网关方式发送出去
Flannel网络概述
Overlay Network:<b>覆盖网络,在基础网络上叠加的一种虚拟网络技术模式,该网络中的主机通过虚拟链路连接起来<br>类似VPN隧道,原理为在物理网络上实现的逻辑网络</b><br>
VXLAN:将源数据包封装到UDP中,并使用基础网络的IP/MAC作为外层包头进行封装,然后在以太网上传输,到达目的地后由隧道断电解封并将数据发给目标地址
Flannel的网络模式
Vxlan 模式(性能较好)
通信流程:https://blog.csdn.net/ver_mouth__/article/details/126343515<br>Node1、2不在一个网段上
不同node上的pod通信流程
<ol><li><span style="font-size: inherit;">pod中的数据,根据pod的路由信息,发送到网桥 cni0</span></li><li><span style="font-size: inherit;">cni0 根据节点路由表,将数据发送到隧道设备flannel.1</span></li><li><span style="font-size: inherit;">flannel.1 查看数据包的目的ip,从flanneld获取对端隧道设备的必要信息,封装数据包</span></li><li><span style="font-size: inherit;">flannel.1 将数据包发送到对端设备。对端节点的网卡接收到数据包,发现数据包为overlay数据包,解开外层封装,并发送内层封装到flannel.1 设备</span></li><li><span style="font-size: inherit;">Flannel.1 设备查看数据包,根据路由表匹配,将数据发送给cni0设备</span></li><li><span style="font-size: inherit;">cni0匹配路由表,发送数据到网桥</span></li></ol>
名词作用
cni0
网桥设备,每创建一个pod都会创建一对 veth pair。其中一段是pod中的eth0,另一端是cni0网桥中的端口
flannel.1
vxlan网关设备,用户 vxlan 报文的解包和封包。不同的 pod 数据流量都从overlay设备以隧道的形式发送到对端。flannel.1不会发送arp请求去获取目标IP的mac地址,而是由Linux kernel将一个"L3 Miss"事件请求发送到用户空间的flanneld程序,flanneld程序收到内核的请求事件后,从etcd中查找能够匹配该地址的子网flannel.1设备的mac地址,即目标pod所在host中flannel.1设备的mac地址
flanneld
在每个主机中运行flanneld作为agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段subnet,本主机内所有容器的IP地址都将从中分配。同时Flanneld监听K8s集群数据库,为flannel.1设备提供封装数据时必要的mac,ip等网络数据信息
VXLAN
Virtual eXtensible Local Area Network,虚拟扩展局域网。采用L2 over L4(MAC-in-UDP)的报文封装模式,将二层报文用三层协议进行封装,实现二层网络在三层范围内进行扩展,同时满足数据中心大二层虚拟迁移和多租户的需求
flannel只使用了vxlan的部分功能,VNI被固定为1。容器跨网络通信解决方案:如果集群的主机在同一个子网内,则通过路由转发过去;若不在一个子网内,就通过隧道转发过去。
部署<br>
$ wget https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml<br> <br># 配置 Pod CIDR <br>$ vi kube-flannel.yml<br> "Network": "10.244.0.0/16", <br> <br># 多网卡时,可指定网卡<br>vi kube-flannel.yml<br> command:<br> - /opt/bin/flanneld<br> args:<br> - --ip-masq<br> - --kube-subnet-mgr<br> - --iface=ens38 # 指定网卡<br> <br>$ kubectl apply -f kube-flannel.yml<br> <br>$ kubectl get pod -n kube-system<br>NAME READY STATUS RESTARTS AGE<br>kube-flannel-ds-8qnnx 1/1 Running 0 10s<br>kube-flannel-ds-979lc 1/1 Running 0 16m<br>kube-flannel-ds-kgmgg 1/1 Running 0 16m
集群节点上网络分配:
$ ip addr<br>6: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default<br> link/ether b6:95:2a:cd:01:c3 brd ff:ff:ff:ff:ff:ff<br> inet 10.244.0.0/32 brd 10.244.0.0 scope global flannel.1<br> valid_lft forever preferred_lft forever<br> inet6 fe80::b495:2aff:fecd:1c3/64 scope link<br> valid_lft forever preferred_lft forever<br>7: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000<br> link/ether 16:ac:e9:68:a4:c0 brd ff:ff:ff:ff:ff:ff<br> inet 10.244.0.1/24 brd 10.244.0.255 scope global cni0<br> valid_lft forever preferred_lft forever<br> inet6 fe80::14ac:e9ff:fe68:a4c0/64 scope link<br> valid_lft forever preferred_lft forever<br> <br>$ ethtool -i cni0<br>driver: bridge<br> <br>$ ethtoo -i flannel.1<br>driver: vxlan<br> <br>$ ps -ef | grep flanneld<br>root 15300 15275 0 10:21 ? 00:00:19 /opt/bin/flanneld --ip-masq --kube-subnet-mgr<br> <br>$ route -n<br>Kernel IP routing table<br>Destination Gateway Genmask Flags Metric Ref Use Iface<br>0.0.0.0 192.168.80.2 0.0.0.0 UG 0 0 0 ens33<br>10.244.0.0 10.244.0.0 255.255.255.0 UG 0 0 0 flannel.1<br>10.244.1.0 10.244.1.0 255.255.255.0 UG 0 0 0 flannel.1<br>10.244.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0<br>192.168.80.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33<br> <br>$ brctl show<br>bridge name bridge id STP enabled interfaces<br>cni0 8000.e2ee89678398 no veth28b04daf<br> vethe6d4a6b8
相关配置
$ cat /etc/cni/net.d/10-flannel.conflist<br>{<br> "name": "cbr0",<br> "cniVersion": "0.3.1",<br> "plugins": [<br> {<br> "type": "flannel",<br> "delegate": {<br> "hairpinMode": true,<br> "isDefaultGateway": true<br> }<br> },<br> {<br> "type": "portmap",<br> "capabilities": {<br> "portMappings": true<br> }<br> }<br> ]<br>}<br> <br>$ cat /run/flannel/subnet.env<br>FLANNEL_NETWORK=10.244.0.0/16<br>FLANNEL_SUBNET=10.244.0.1/24<br>FLANNEL_MTU=1450<br>FLANNEL_IPMASQ=true<br> <br># Bridge CNI 插件<br>$ cat /var/lib/cni/flannel/462cf658ef71d558b36884dfb6d068e100a3209d36ba2602ad04dd9445e63684 | python3 -m json.tool<br>{<br> "cniVersion": "0.3.1",<br> "hairpinMode": true,<br> "ipMasq": false,<br> "ipam": {<br> "routes": [<br> {<br> "dst": "10.244.0.0/16"<br> }<br> ],<br> "subnet": "10.244.2.0/24",<br> "type": "host-local"<br> },<br> "isDefaultGateway": true,<br> "isGateway": true,<br> "mtu": 1450,<br> "name": "cbr0",<br> "type": "bridge"<br>}
卸载
# 主节点<br>kubectl delete -f kube-flannel.yml<br> <br># 所有节点上<br>ip link set cni0 down<br>ip link set flannel.1 down<br> <br>ip link delete cni0<br>ip link delete flannel.1<br> <br>rm -rf /var/lib/cni/<br>rm -f /etc/cni/net.d/*
hostgw(纯三层网络的方案,性能最高)
<b>所有宿主机都在一个局域网内,跨局域网无法进行路由</b><br>通信流程:https://img-blog.csdnimg.cn/40729bb770cf436ea85909634c2f9901.png<br>
实现原理:
<b>host-gw采用纯静态路由的方式,要求所有宿主机都在一个局域网内,跨局域网无法进行路由</b>。如果需要进行跨局域网路由,需要在其他设备上添加路由,但已超出flannel的能力范围。可选择calico等使用动态路由技术,通过广播路由的方式将本机路由公告出去,从而实现跨局域网路由学习。
所有的子网和主机的信息,都保存在Etcd中,flanneld只需要watch这些数据的变化 ,实时更新路由表
核心:IP包在封装成桢的时候,使用路由表的“下一跳”设置上的MAC地址,这样可以经过二层网络到达目的宿主机
部署
$ vi kube-flannel.yml<br> "Backend": {<br> "Type": "host-gw"<br> }<br> <br>$ kubectl apply -f kube-flannel.yml<br> <br>$ kubectl get pod -n kube-system<br>NAMESPACE NAME READY STATUS RESTARTS AGE<br>kube-system kube-flannel-ds-l2dg7 1/1 Running 0 7s<br>kube-system kube-flannel-ds-tj2vg 1/1 Running 0 7s<br>kube-system kube-flannel-ds-xxhfm 1/1 Running 0 7s
集群节点上网络分配
$ ip addr<br>7: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000<br> link/ether 2a:00:05:23:3f:5e brd ff:ff:ff:ff:ff:ff<br> inet 10.244.2.1/24 brd 10.244.2.255 scope global cni0<br> valid_lft forever preferred_lft forever<br> inet6 fe80::2800:5ff:fe23:3f5e/64 scope link<br> valid_lft forever preferred_lft forever<br> <br>$ kubectl logs kube-flannel-ds-l2dg7 -n kube-system<br>I1227 12:09:56.991787 1 route_network.go:86] Subnet added: 10.244.2.0/24 via 192.168.80.240<br>I1227 12:09:56.992305 1 route_network.go:86] Subnet added: 10.244.0.0/24 via 192.168.80.241<br> <br>$ route -n<br>Kernel IP routing table<br>Destination Gateway Genmask Flags Metric Ref Use Iface<br>0.0.0.0 192.168.80.2 0.0.0.0 UG 0 0 0 ens33<br>10.244.0.0 192.168.80.241 255.255.255.0 UG 0 0 0 ens33<br>10.244.1.0 192.168.80.242 255.255.255.0 UG 0 0 0 ens33<br>10.244.2.0 0.0.0.0 255.255.255.0 U 0 0 0 cni0<br>192.168.80.0 0.0.0.0 255.255.255.0 U 0 0 0 ens33
相关配置
$ cat /etc/cni/net.d/10-flannel.conflist<br>{<br> "name": "cbr0",<br> "cniVersion": "0.3.1",<br> "plugins": [<br> {<br> "type": "flannel",<br> "delegate": {<br> "hairpinMode": true,<br> "isDefaultGateway": true<br> }<br> },<br> {<br> "type": "portmap",<br> "capabilities": {<br> "portMappings": true<br> }<br> }<br> ]<br>}<br> <br>$ cat /run/flannel/subnet.env<br>FLANNEL_NETWORK=10.244.0.0/16<br>FLANNEL_SUBNET=10.244.0.1/24<br>FLANNEL_MTU=1500 # 路由方式下,MTU值使用默认值<br>FLANNEL_IPMASQ=true<br> <br># Bridge CNI 插件<br>$ cat /var/lib/cni/flannel/46c76c1d50d61494d6d95e0171667ec705bbcdcaeeafa859e25ac4749979bd76 | python3 -m json.tool<br>{<br> "cniVersion": "0.3.1",<br> "hairpinMode": true,<br> "ipMasq": false,<br> "ipam": {<br> "ranges": [<br> [<br> {<br> "subnet": "10.244.2.0/24"<br> }<br> ]<br> ],<br> "routes": [<br> {<br> "dst": "10.244.0.0/16"<br> }<br> ],<br> "type": "host-local"<br> },<br> "isDefaultGateway": true,<br> "isGateway": true,<br> "mtu": 1500,<br> "name": "cbr0",<br> "type": "bridge"<br>}
UDP模式(性能差)
公司没用---
三种网络模式对比
udp模式
使用设备flannel.0进行封包解包,不是内核原生支持,上下文切换较大,性能非常差
vxlan模式
使用flannel.1进行封包解包,内核原生支持,性能损失在20%~30%左右
host-gw模式
无需flannel.1这样的中间设备,直接宿主机当作子网的下一跳地址,性能损失大约在10%左右
Calico
什么是calico?
1、calico是一个虚拟网络解决方案,<b>它完全利用路由规则实现动态组网,通过BGP协议通告路由<br></b>2、用于容器、虚拟机、宿主机之前的网络连接,可以用在kubernetes、OpenShift、DockerEE、OpenStrack等PaaS或IaaS平台上
Calico 组件概述
Calico架构图
https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/calico%E6%9E%B6%E6%9E%84%E5%9B%BE.png
Calico优势与劣势
优势
1、endpoints组成的网络是单纯的三层网络,报文的流向完全通过路由规则控制,没有overlay等额外开销(没有封包和解包过程,完全基于两端宿主机的路由表进行转发)
2、可以配合使用 Network Policy 做 pod 和 pod 之前的访问控制<br>(https://blog.csdn.net/xixihahalelehehe/article/details/108422856)
劣势
1、<b>要求宿主机处于同一个2层网络下,也就是连在一台交换机上</b>
2、路由的数目与容器数目相同,非常容易超过路由器、三层交换、甚至node的处理能力,从而限制了整个网络的扩张。(可以使用大规模方式解决)
3、每个node上会设置大量(海量)的iptables规则、路由,运维、排障难度大
4、原理决定了它不可能支持VPC,容器只能从calico设置的网段中获取ip<br>vpc: https://help.aliyun.com/document_detail/86500.html
5、calico的网络规模受到BGP网络规模的限制<br>
Calico组件概述
Felix
calico的核心组件,运行在每个节点上。主要的功能有:<br>
1、接口管理:Felix为内核编写一些接口信息,以便让内核能正确的处理主机endpoint的流量。特别是主机之间的ARP请求和处理ip转发
2、路由规则:Felix负责主机之间路由信息写到linux内核的FIB(Forwarding Information Base)转发信息库,保证数据包可以在主机之间相互转发
3、ACL规则:Felix负责将ACL策略写入到linux内核中,保证主机endpoint的为有效流量不能绕过calico的安全措施
4、状态报告:Felix负责提供关于网络健康状况的数据。特别是,它报告配置主机时出现的错误和问题。这些数据被写入etcd,使其对网络的其他组件和操作人员可见
Etcd
保证数据一致性的数据库,存储集群中节点的所有路由信息。为保证数据的可靠和容错建议至少三个以上etcd节点。
Orchestrator plugin
协调器插件负责允许kubernetes或OpenStack等原生云平台方便管理Calico,可以通过各自的API来配置Calico网络实现无缝集成。如kubernetes的cni网络插件
Bird(架构图中的Bird client)
1、BGP客户端,Calico在每个节点上的都会部署一个BGP客户端,它的作用是将Felix的路由信息读入内核,并通过BGP协议在集群中分发
2、当Felix将路由插入到Linux内核FIB中时,BGP客户端将获取这些路由并将它们分发到部署中的其他节点。这可以确保在部署时有效地路由流量
BGP Router Reflector
大型网络仅仅使用 BGP client 形成 mesh 全网互联的方案就会导致规模限制,所有节点需要 N^2 个连接,为了解决这个规模问题,可以采用 BGP 的 Router Reflector 的方法,使所有 BGP Client 仅与特定 RR 节点互联并做路由同步,从而大大减少连接数
Calicoctl
calico 命令行管理工具
其他名词解释
endpoint: 接入到calico网络中的网卡称为endpoint
AS: 网络自治系统,通过BGP协议与其它AS网络交换路由信息
ibgp: AS内部的BGP Speaker,与同一个AS内部的ibgp、ebgp交换路由信息
ebgp: AS边界的BGP Speaker,与同一个AS内部的ibgp、其它AS的ebgp交换路由信息
workloadEndpoint: 虚拟机、容器使用的endpoint
hostEndpoints: 物理机(node)的地址<br>
Calico 网络模式<br>
BGP 边界网关协议(Border Gateway Protocol):是互联网上一个核心的去中心化自治路由协议。BGP不使用传统的内部网关协议(IGP)的指标<br>
BGP 概述<br>
1、BGP(border gateway protocol)是外部路由协议(边界网关路由协议),用来在AS之间传递路由信息是一种增强的距离矢量路由协议(应用场景),基本功能是在自治系统间自动交换无环路的路由信息,通过交换带有自治系统号序列属性的路径可达信息,来构造自治系统的拓扑图,从而消除路由环路并实施用户配置的路由策略
<b>实际上,Calico 项目提供的 BGP 网络解决方案,与 Flannel 的 host-gw 模式几乎一样。也就是说,Calico也是基于路由表实现容器数据包转发,但不同于Flannel使用flanneld进程来维护路由信息的做法,而Calico项目使用BGP协议来自动维护整个集群的路由信息</b>
边界网关协议BGP是什么?
1、(边界网关协议(BGP),提供自治系统之间无环路的路由信息交换(无环路保证主要通过其AS-PATH实现),BGP是基于策略的路由协议,其策略通过丰富的路径属性(attributes)进行控制
2、BGP工作在应用层,在传输层采用可靠的TCP作为传输协议(BGP传输路由的邻居关系建立在可靠的TCP会话的基础之上)
3、在路径传输方式上,BGP类似于距离矢量路由协议。而BGP路由的好坏不是基于距离(多数路由协议选路都是基于带宽的),它的选路基于丰富的路径属性,而这些属性在路由传输时携带,所以我们可以把BGP称为路径矢量路由协议
4、如果把自治系统浓缩成一个路由器来看待,BGP作为路径矢量路由协议这一特征便不难理解了。除此以外,BGP又具备很多链路状态(LS)路由协议的特征,比如触发式的增量更新机制,宣告路由时携带掩码等
为什么叫边界网关协议呢?
和 flannel host-gw 工作模式基本上一样,BGP是一个边界路由器,主要是在每个自治系统的最边界与其他自治系统的传输规则,而这些节点之间组成的BGP网络是一个全网通的网络,这个网络就称为一个BGP Peer
启动文件放在 /opt/cni/bin 目录下,/etc/cni/net.d 目录下记录子网的相关配置信息。
BGP两种模式<br>
全互联模式<b></b>
每一个BGP Speaker都需要和其他BGP Speaker建立BGP连接,这样BGP连接总数就是N^2,如果数量过大会消耗大量连接。<b>如果集群数量超过100台官方不建议使用此种模式</b>
架构图:https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/Calico-BGP%E6%A8%A1%E5%BC%8F%E6%9E%B6%E6%9E%84%E5%9B%BE.png
<b>从架构图中可以看到,Pod 1 访问 Pod 2 流程如下:<br></b>1、数据包从 Pod1 出到达Veth Pair另一端(宿主机上,以cali前缀开头)<br>2、宿主机根据路由规则,将数据包转发给下一跳(网关)<br>3、到达 Node2,根据路由规则将数据包转发给 cali 设备,从而到达 Pod2。<br>
其中,这里最核心的 下一跳 路由规则,就是由 Calico 的 Felix 进程负责维护的。这些路由规则信息,则是通过 BGP Client 中 BIRD 组件,使用 BGP 协议来传输
Calico 项目实际上将集群里的所有节点,都当作是边界路由器来处理,它们一起组成了一个全连通的网络,互相之间通过 BGP 协议交换路由规则。这些节点,我们称为 BGP Peer
而 Flannel host-gw 和 Calico 的唯一不一样的地方就是当数据包下一跳到达node2节点容器时发生变化,并且出数据包也发生变化,知道它是从veth的设备流出,容器里面的数据包到达宿主机上,这个数据包到达node2之后,它又根据一个特殊的路由规则,这个会记录目的通信地址的cni网络,然后通过cali设备进去容器,这个就跟网线一样,数据包通过这个网线发到容器中,这也是一个二层的网络互通才能实现
路由反射模式Router Reflection(RR)
RR模式 中会指定一个或多个BGP Speaker为RouterReflection,它与网络中其他Speaker建立连接,每个Speaker只要与Router Reflection建立BGP就可以获得全网的路由信息。在calico中可以通过Global Peer实现RR模式
设置方法请参考官方链接 https://docs.projectcalico.org/master/networking/bgp
为什么要使用RR模式?
Calico 维护的网络在默认是 (Node-to-Node Mesh)全互联模式,Calico集群中的节点之间都会相互建立连接,用于路由交换。但是随着集群规模的扩大,mesh模式将形成一个巨大服务网格,连接数成倍增加。这时就需要使用 Route Reflector(路由器反射)模式解决这个问题。确定一个或多个Calico节点充当路由反射器,让其他节点从这个RR节点获取路由信息
在BGP中可以通过calicoctl node status看到启动是 node-to-node mesh 网格的形式,这种形式是一个全互联的模式,默认的BGP在k8s的每个节点担任了一个BGP的一个喇叭,一直吆喝着扩散到其他节点,随着集群节点的数量的增加,那么上百台节点就要构建上百台链接,就是全互联的方式,都要来回建立连接来保证网络的互通性,那么增加一个节点就要成倍的增加这种链接保证网络的互通性,这样的话就会使用大量的网络消耗,所以这时就需要使用Route reflector,也就是找几个大的节点,让他们去这个大的节点建立连接,也叫RR,也就是公司的员工没有微信群的时候,找每个人沟通都很麻烦,那么建个群,里面的人都能收到,所以要找节点或着多个节点充当路由反射器,建议至少是2到3个,一个做备用,一个在维护的时候不影响其他的使用
IPIP模式
IPIP模式原理:
IPIP 是linux内核的驱动程序,可以对数据包进行隧道,上图可以看到两个不同的网络 vlan1 和 vlan2。基于现有的以太网将原始包中的原始IP进行一次封装,通过tunl0解包,这个tunl0类似于ipip模块,和Flannel vxlan的veth很类似
架构图:<br>https://github.com/tzh666/k8s-yaml/blob/main/k8s-network/Calico-IPIP%E6%A8%A1%E5%BC%8F%E6%9E%B6%E6%9E%84%E5%9B%BE.png<br>
从架构图中,Pod1 访问 Pod2 流程如下:<br>1、数据包从 Pod1 出到达Veth Pair另一端(宿主机上,以cali前缀开头)。<br><br>2、进入IP隧道设备(tunl0),由Linux内核IPIP驱动封装,把源容器ip换成源宿主机ip,目的容器ip换成目的主机ip,这样就封装成 Node1 到 Node2 的数据包。<br>3、数据包经过路由器三层转发到 Node2。<br><br>4、Node2 收到数据包后,网络协议栈会使用IPIP驱动进行解包,从中拿到原始IP包。<br><br>5、然后根据路由规则,将数据包转发给cali设备,从而到达 Pod2。<br>
此时包的类型:<br> 原始IP包:<br> 源IP:10.244.1.10<br> 目的IP:10.244.2.10<br><br> TCP:<br> 源IP: 192.168.31.62<br> 目的iP:192.168.32.63<br>
实际查看路由情况
https://blog.csdn.net/qq_23435961/article/details/106660196
Calico 管理工具<br>
calicoctl 工具安装<br>
# 下载工具:https://github.com/projectcalico/calicoctl/releases<br><br>$ wget -O /usr/local/bin/calicoctl https://github.com/projectcalico/calicoctl/releases/download/v3.13.3/calicoctl<br><br>$ chmod +x /usr/local/bin/calicoctl<br># 查看集群节点状态<br><br>$ calicoctl node status<br>
使用 calicoctl get node,需要指定 calicoctl 配置,默认使用 /etc/calico/calicoctl.cfg
# 设置 calicoctl 配置文件<br>$ vim /etc/calico/calicoctl.cfg<br><br>apiVersion: projectcalico.org/v3<br>kind: CalicoAPIConfig<br>metadata:<br>spec:<br> datastoreType: "etcdv3"<br> etcdEndpoints: https://10.10.0.174:2379<br> etcdKeyFile: /opt/kubernetes/ssl/server-key.pem<br> etcdCertFile: /opt/kubernetes/ssl/server.pem<br> etcdCACertFile: /opt/kubernetes/ssl/ca.pem<br><br># 查看 calico 节点<br>$ calicoctl get nodes<br><br># 查看 IPAM的IP地址池<br>$ calicoctl get ippool -o wide<br><br># 查看bgp网络配置情况<br>$ calicoctl get bgpconfig<br><br># 查看ASN号,一个编号就是一个自治系统<br>$ calicoctl get nodes --output=wide<br><br># 查看 bgp peer<br>$ calicoctl get bgppeer<br>
CNI 网络方案优缺点及最终选择<br>
先考虑几个问题:<br><br>1、需要细粒度网络访问控制?<br><br>这个flannel是不支持的,calico支持,所以做多租户网络方面的控制ACL,那么要选择 calico。<br><br>2、追求网络性能?<br><br>选择 flannel host-gw 模式 和 calico BGP 模式。<br><br>3、服务器之前是否可以跑BGP协议?<br><br>很多的公有云是不支持跑BGP协议,那么使用calico的BGP模式自然是不行的。<br><br>4、集群规模多大?<br><br>如果规模不大,100以下节点可以使用flannel,优点是维护比较简单。<br><br>5、是否有维护能力?<br><br>1、需要细粒度网络访问控制?<br><br>这个flannel是不支持的,calico支持,所以做多租户网络方面的控制ACL,那么要选择 calico。<br><br>2、追求网络性能?<br><br>选择 flannel host-gw 模式 和 calico BGP 模式。<br><br>3、服务器之前是否可以跑BGP协议?<br><br>很多的公有云是不支持跑BGP协议,那么使用calico的BGP模式自然是不行的。<br><br>4、集群规模多大?<br><br>如果规模不大,100以下节点可以使用flannel,优点是维护比较简单。<br><br>5、是否有维护能力?<br><br>calico的路由表很多,而且走BGP协议,一旦出现问题排查起来也比较困难,上百台的,路由表去排查也是很麻烦,这个具体需求需要根据自己的情况而定。
iptable笔记
k8s运维管理
k8s开发指南
Trouble Shooting指南
Kubernetes是什么?
1、Kubernetes是由谷歌内部开源的容器编排工具<br>2、Kubernetes是一个开放的开发平台<br>3、Kubernetes是是一个完备的分布式系统支撑平台,具有完备的集群管理能力,包括多层次的安全防护和准入控制、多租户应用支撑能力、透明的服务注册和服务发现机制、内建智能的负载调度均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力。
Kubernetes资源对象<br>
Pod
Service
Node
PV
对k8s资源对象的事物与动作
Label、Label选择器
注解Annotion
Namespace
Deplpyment
StatefuSet
DaemonSet
PVC
CronJob
Job<br>
Kubernetes的基本概念和术语
集群类
一个kubernetes集群由Master和Node节点组成
Master
Master指的是集群的控制节点,在每个k8s集群中都需要一个或者一组被称为Master的节点来负责集群的管理和控制
Master通常占据一个独立的服务器,在高可用中至少部署3台服务器,3、5、7、9
Master上运行的关键服务
kube-apiserver
提供HTTP RESTful API接口的主要服务,是k8s里对所有资源 进行CRUD等操作的唯一入口<br>
是集群内各个功能模块之间数据交互和通信的中心枢纽
提供完备的集群安全机制
kube-controller-manager
k8s里所有资源对象的自动化控制中心,通过apiserver监控整个集群的状态,并确保集群处于预期的工作状态<br>
kube-scheduler
负责资源调度、pod调度<br>
一般来说etcd数据库也部署在Master节点上
Node
在k8s集群中,除了Master节点的其他节点都是Node节点
1、Node节点是集群中的工作负载节点,每个Node节点都会被Master分配一些工作负载<br>2、当某个Node节点宕机时,其上的工作负载会被master自动转移到其他的Node上
Node上运行的关键服务
kubelet
负责Pod对应容器的创建、启停等任务,同时与Master密切合作,实现集群管理的基本功能
镜像和容器的清理工作保证节点上镜像不会占满磁盘空间,退出的容器不会占用太多资源
kube-proxy<br>
是K8S在每个节点上运行网络代理, service资源的载体
建立了pod网络和集群网络的关系( clusterip >podip )<br>
常用三种流量调度模式
Userspace (废弃 )<br>
Iptables (濒临废弃)
Ipvs(推荐)
负责建立和删除包括更新调度规则、通知apiserver自己的更新,或者从apiserver哪里获取其他kube-proxy的调度规则变化来更新自己的
群集管理命令
查看所有节点
kubectl get no
修改证书资源(不常用)
kubectl certificate
approve
批准证书签名请求
deny
拒绝证书签名请求<br>
显示集群信息
kubectl cluster-info
要进一步调试和诊断集群问题,可以使用 kubectl cluster-info dump 命令查看信息
显示资源(CPU/内存/存储)使用情况
kubectl top node
Node调度状态管理
标记为不可调度状态
kubectl cordon 节点名字
标记为可调度的状态
kubectl uncordon 节点名字
驱逐节点上的pod
kubectl drain 节点主机名 --delete-local-data --force --ignore-daemonsets<br>
这条命令做了两件事情<br>
设定此node为不可调度状态(cordon)
Node状态会多一个SchedulingDisabled标识
kubectl uncordon node04 重新标记为可调度节点即可
evict(回收)了其上的三个pod
<b><font color="#d32f2f">节点升级的时候会需要这个操作</font></b>
删除节点
kubectl delete 节点名字
查看某个节点详细信息
kubectl describe node nodeName
Node的基本信息:名称、标签、创建时间等
Node当前的运行状态:Node启动前会做一系列的自检工作,比如磁盘、内存、网络、PID等资源是否充足,都正常的情况下Node的状态为Ready,那么Pod就能正常调度到这节点上
Node主机名、IP
Node上的资源数量:描述Node可用的系统资源、包括CPU、内存、最大调度的pod数量(默认是110)等
Node可分配的资源量:描述Node节点当前可用于分配的资源量
主机信息:包括主机ID、系统UUID、内核版本号、操作系统类型版本、CPU架构、Docker版本、kubelet、kube-kuroxy版本号等<br>
当前已经运行的Pod列表概要信息
已分配的资源使用概要信息,例如资源申请的最小、最大允许占用系统总量的百分比,例如磁盘使用率85
Node的相关Event信息
应用类
service(服务)
Servide一般指的是无状态服务,通常由多个程序副本提供服务(ep+port)<br>当然也可以是有状态的单例服务,例如MySQL这种数据存储类的服务
k8s里的svc具有全局唯一一个的虚拟Cluster地址,且创建svc的时候k8s会自动分配这个虚拟IP,且svc的生命周期中这虚拟IP是不会改变的
<b>特殊的Headless</b>
这种类型的service在定义中设置了clusterIP: Node,定义了这个以后这个SVC就没有了ClusterIP地址<br>
如果解析这种Headless Service的DNS域名,则返回的是svc对应的全部Pod的ep列表,这意味着客户端是直接与后端的Top进行TCP/IP连接进行通信的,没有通过虚拟IP地址进行转发,因此通信性能最高,<b>等同于 "原生网络通信"</b>
Service的外网访问问题
首先k8s里面有三种IP
Node IP
Node的IP地址
这IP地址是真实存在的节点物理地址
Pod IP
Pod 的IP地址
是Pod的IP地址,在使用Docker做为容器的支持引擎的情况下,它是Docker Engine根据Docker0网桥的IP地址进行分配的,通常是一个虚拟二层网络
Service IP
Service的Cluster IP地址属于集群内的地址,无法在集群外部直接使用这个地址,所以就有了Node Port这个概念
type=NodePort
公有云上type=LoadBalancer,此时会自动创建一个对应的负载均衡实例并返回它的真实IP地址,供外部使用
Pod
每一个Pod可以由多个容器组成,但是每个Pod都有一个特殊的Pause容器,这个Pause被称之为根容器
Pod这种全新的概念,组成结构原因?
为多进程之前的协助提供抽象模型,使用Pod作为最基本的调度、复制等管理工作的最小单位,让多个应用进程能一起有效的调度和伸缩
Pod里面多个业务容器共享Pause的容器IP,共享Pause容器挂载的Volume,这样既解决了密切关联的业务容器之间的通信问题,也解决了它们之间的文件共享问题
一个Pod只有一个PodIP,Pod里面的容器都共享这个IP
Pod分类
普通Pod
普通的Pod一旦被创建就会被存入到etcd中存储,且能调度到指定的Node上,如果这个Node挂了Pod也会重新调度
静态Pod
不能通过kubectl管理,只能放到指定node节点的具体目录中,并且只能在此Node上运行
Pod的Event
Event是一个事件的记录,记录着事件产生的最早时间、最后重现时间、重复次数、发起者、类型,以及导致此事件发生的原因等众多信息
Endpoint
代表着Pod里的一个服务进程对外的通信地址
一个Pod具有多个Endpoint的情况,例如部署一个tomcat可以对外暴露管理端口与服务端口这两个Endpoint
Label与标签选择器(Selectot)
Label
1、一个Label是一个Key=Value的键值对,其中的key与value由用户自己指定<br>2、Label可以被附加到各种资源对象上,例如Node、Pod、Service、deployment等<br>3、一个资源对象可以定义任意数量的Label,用一个Label也可以被添加到任意数量的资源对象上<br>4、Label可以在对象创建的时候确定,也可以在对象的创建以后动态的添加或者删除
Selector
给某个对象定义了一个Label,就相当于给他打了一个标签,随后就可以通过Selector查询和筛选拥有某些Label的资源对象
类似于MySQL的查询语句<br>select * from pod where name= "redis-dev"
Selector分类
基于等式
name = redis-slave
evn != pro
env = test
基于集合
name in (redis-slave,redis-master)
name not in (php)
# 同时满足多个关系--> and<br>name=redis-slave,env!=pro<br>
Job与CronJob
Job
只能运行一次,当Job控制的所有Pod副本运行结束时,对应的Job也就结束了
CronJob可以周期性的执行某个任务
ConfigMap与Secret
HPA与VPA
存储类
Volume
Volume是Pod中能够被多个容器访问的共享目录,k8s中的volume跟docker中的volume比较类似,但是二者不能等价
首先k8s中的volume被定义在Pod上,被一个Pod的多个容器挂载到具体的目录中
其次k8s中的volume与Pod的生命周期相同,但是与容器的生命周期不相关,就是当容重启时,Volume中的数据也不会丢失
最后k8s支持多种类型的Volume,例如GFS、ceph等多种分布式文件系统
Volume常见类型说明
emptyDir
1、Pod创建时自动创建的<br>2、初始目录为空<br>3、无需指定宿主机上的目录文件
用途
临时空间,用于某些应用程序运行时所需目录,且无需永久保留
长时间任务执行过程中使用的临时目录
多容器共享目录,当一个容器需要从另一个容器中或者数据的目录
在默认的情况下,emptyDir使用的节点的存储介质,例如磁盘、网络存储
具体设置 emytpDir.medium属性
hostPast
hsotPath为在Pod上挂载宿主机的文件或者目录,<b>也就是挂载宿主机的文件或者目录到Pod中</b>
用途
存储程序生成日志或者数据
想访问Docker 引擎内部数据结构的容器时,可以挂载docker数据目录出来
公有云Volume
当我们的k8s集群运行在公有云或者公有云提供的k8s集群时,就可以使用这类Volume
谷歌云
GCEPersistentDisk
亚马逊云
EBS Volume
其他类型的Volume
iscsi
nfs
gfs
rdb
ceph的设备共享存储挂载到Pod中
gitRepo
通过挂载一个空目录,并从Git库克隆一个git repository以供Pod使用
cm
secret
Persistent Volume
简称PV,有系统动态创建的一个存储卷,可以被理解为k8s集群中某个网络存储对应的一块存储,与Volume类似,也是会在宿主机有一个对应的目录持久化容器中数据,但是PV并不是定义在Pod上,而已是独立于Pod之外的资源清单
PV目前支持的主要类型有
谷歌云<br>GCEPersistentDisk<br>
<div class="mind-clipboard">亚马逊云<br>EBS Volume</div>
AzureFile
AzureDisk
FC
NFS
ISCSI
RBD
CephFS
cinder
GFS
HostPath
Local
等等等
PVC
绑定pv
StorageClass
Storage Class可以帮我们支持创建pv时,使用的第三方插件
安全类
RBAC访问控制权限系统
每个ns都会创建一个default的Service Accent,因此SA是不能全局使用的,只能被他所在的空间中的Pod使用
Service Accent是什么?
Service Accent 是通过secret来保存对应的用户的身份凭证,这些凭证包括CA跟证书数据(ca.crt)和签名后的Token信息
这文件会存的Pod的一个指定目录中,默认是在:
Role
RoleBinding
ClusterRole
ClusterRoleBinding
深入掌握Pod编排
Pod资源清单
Pod完整的内容<br>https://www.cnblogs.com/hsyw/p/14169045.html<br>
特别关注的有
# 镜像拉取策略<br>spec.containers.imagePullPolicy
# 默认是Always: 表示每次都重新拉取镜像<br># IfNoPresent: 表示本地有镜像则使用该镜像,如果不存在再拉取镜像<br># Never: 表示仅使用本地镜像<br># 不指定镜像tag时,默认使用latest<br>
启动参数
# 容器的启动命令列表,如果不指定,则使用打镜像包时使用的启动命令,也就是Dockerfile的ENTRYPOINT<br>spec.containers.command
# 容器的启动命令参数列表<br>spec.containers.args
两参数关系<br>
<b># command、args两项实现覆盖Dockerfile中ENTRYPOINT的功能,具体的command命令代替ENTRYPOINT的命令行,args代表集体的参数。<br><br># 以下是使用场景<br>1. 如果command和args均没有指定,那么则使用Dockerfile的配置。<br><br>2. 如果command没有指定,但指定了args,那么Dockerfile中配置的ENTRYPOINT的命令行会被执行,并且将args中填写的参数追加到ENTRYPOINT中。<br><br>3. 如果command指定了,但args没有写,那么Dockerfile默认的配置会被忽略,执行输入的command(不带任何参数,当然command中可自带参数)。<br><br>4. 如果command和args都指定了,那么Dockerfile的配置被忽略,执行command并追加上args参数。</b>
# Pod重启策略<br>spec.restartPolicy<br>
默认值是Always:一旦Pod终止运行,且不管Pod是如何终止的,kubelet都将重启它
OnFailure:只有当Pod以非零退出终止时(例如oom),kubelet才会重启该容器,如果容器正常结束(退出代码为0),则kubelet将不会重启该容器
Never:Pod终止后,kubelet将终止代码传递给Master,不会再重启Pod
# 可选,拉取镜像使用的secret,可以配置多个<br># 以name: secertkey的格式指定<br>imagePullSecrets<br>
# 可选,是否为主机模式,如是,会占用主机端口<br># 且该Pod在同一台机器上无法启动第二个副本(端口冲突)<br>hostNetwork<br>
# 容器所在主机需要监听的端口,默认与containerPort相同<br># 设置hostPost时,同一台宿主机将无法启动该容器的第二份副本<br>spec.containers.ports.hostPort
Pod使用前说明
在使用Docker时,可以使用docker run启动一个容器
而在k8s系统中对长时间运行容器的要求是:<b>其主程序需要一直在前台运行。如果Dockerfile创建的镜像启动命令是后台执行<br></b>例如 nohup ./start.sh & , 则kubelet 创建包含这个容器的Pod之后运行完该命令(nohup ./start.sh &) 即认为改Pod执行结束,将立即销毁这Pod。如果这个Pod定义了副本,那么会无线循环创建、终止,所以这就是k8s需要我们自己创建DOcker镜像必须以一个前台命令作为启动命令的原因
静态Pod
1、静态Pod是由kubelet进行管理的仅存于在特定节点上的Pod<br>2、它们不能通过API SERVER进行管理,无法与ReplicationControoller、Deployment或者DS进行关联<br>3、且kubelet无法对它进行检查检查<br>4、静态Pod总是由kubelet创建的,并且总在kubelet所在的Node运行(就是这个静态Pod的配置文件在哪个节点就在那节点运行)
静态Pod创建方式
配置文件方式
# 设置kubelet启动参数<br>--pod-manifest-path,这个参数会逐渐被废除<br>或者在kubelet中设置staticPodPath,这是新版本推荐的方式
HTTP方式
在kubelet启动参数 --manifest-url<br>kubelet 会定期从改URL地址下载Pod的配置文件,并以yaml or json格式解析,然后创建Pod,其实现方式与配置文件的形式是一致的
Pod容器共享Volume
同一个Pod中的多个容器可以共享Pod级别的存储卷Volume
具体例子请看pod-volume-applog.yaml
# 配置文件链接地址<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/pvd-volume-applogs.yaml<br>
意思就是在一个Pod内包含了两个容器:tomcat、busybox,在Pod级别设置Volume "app-logs",<br>tomcat容器向其写日志,busybox容器从中读取日志<br><br>看配置文件,两个容器内部的volumeMounts的名字都是app-logs,那么此时都会挂载到同一个Pod级别的volume "app-logs" 中,所以这就是同一个Pod能共享Pod级别的volume存储卷
Pod的配置管理
ConfigMap
为什么要使用ConfigMap呢?
部署一个应用的最佳时间是讲应用所需的配置信息与程序分离,这样可以使程序更好的被复用,通过挂载不同的配置也能实现更灵活的功能。所以将应用打包为镜像后,可以通过环境变量或者外挂文件的方式在创建容器时进行配置注入
cm的典型用法
生成容器内的环境变量
设置容器启动命令的启动参数(需设置成环境变量)
以volume的形式挂载到容器内部的文件或者目录
cm的创建方式
通过yaml文件方式创建
# 配置文件链接地址<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-configmap/cm-appvars.yaml<br>
通过kubectl命令行方式创建
# 参数创建,且可以在一个命令行中指定多个参数<br>--from-file
# <b>通过--from-file参数文件中创建</b>,可以指定key名称, 也可以在命令行中创建包括多个key的cm <br><b>kubectl create cm NAME --from-file=[key=]sourec --from-file=[key=]sourec</b><br>
# 例子,假设当前目录有个server.xml配置文件<br><b>kubectl create cm cm-server.xml --from-file=server.xml</b>
# <b>通过--from-file参数目录中创建</b>,该文件夹中每个文件名都会被设置为key,文件的内容则对应设置为value<br><b>kubectl create cm NAME --from-file=cm-dir-files</b><br>
# 例子,假设该目录中有n个文件<br><b>kubectl create cm cm-appconf --from-file=configfiles/</b><br>
# 参数创建,且可以在一个命令行中指定多个参数<br>--from-literal
# <b>通过--from-literal参数会从文本(直接在命令行中设置key跟value)中进行创建</b>,直接指定key#=value创建为cm的内容<br><b>kubectl create cm NAME --from-literal=key1=value1 --from-literal=key2=value2</b><br>
# 例子,命令行直接设置key、value值<br><b>kubectl create cm cm-appenv --from-literal=loglevel=info --from-literal=appdatadir=/var/data</b>
在Pod中使用cm
通过环境变量方式使用cm
# 使用之前的cm挂载到pod中,https://github.com/tzh666/k8s-yaml/blob/main/pod-configmap/cm-appvars.yaml
# 使用关键字env一个个从cm中设置环境变量<br><br># 配置文件地址 https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/cm-test-pod.yaml
# 使用关键字 envFrom, 指定挂载的cm后会根据cm中的key=value,自动生成环境变量,有多少对kv就生成多少对环境变量<br># 配置文件地址 https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/cm-test-pod.yaml<br>
# 创建了Pod以后,通过kubectl logs cm-test-pod均可看到两种方式挂载cm成为环境变量打印的值<br>[root@master01 pod-yaml]# kubectl logs cm-test-pod <br>APPDATADIR=/var/data<br>APPLOGLEVEL=info<br>
特殊说明:环境变量名称受POSIX名称规范,不能以数字开头,或者包含非法字符,会创建失败,pod也会启动失败<br>此时会记录一个Event来记录此事件
通过volumeMount使用cm
指定items
当cm中有多个data,我们可以通过关键字items将cm的data 遍历出来,自定义挂载文件名字,挂载到容器中
# 配置文件链接<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/cm-test-app.yaml<br>
不指定items
不指定items,那么会直接以data的key当作文件名字挂载到目录中,不过这时候可以提前命名好key,写成实际的文件名即可<br><br># 查看是否挂载成功<br>kubectl exec -it cm-test-app -- ls /configfiles<br>
# 配置文件链接<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/cm-test-app.yaml<br>
使用cm的限制条件
ConfigMap必须在Pod之前创建,Pod才能引用它
ConfigMap无法用于静态Pod
ConfidMap受于ns的限制,处于同一个ns的pod才能引用它
如果Pod使用envFrom基于ConfigMap定义环境变量,那么无效的环境变量名字将被忽略(例如名称以数字开头),并且事件记录为InvalidVariableNames
Secret
Secret 主要使用的类型
Opaque<br>
base64 编码格式的 Secret,用来存储密码、密钥等;但数据也可以通过base64 –decode解码得到原始数据,所有加密性很弱
kubernetes.io/dockerconfigjson
用来存储私有docker registry的认证信息
kubernetes.io/service-account-token
用于 ServiceAccount, ServiceAccount 创建时 Kubernetes 会默认创建一个对应的 Secret 对象,Pod 如果使用了 ServiceAccount,对应的 Secret 会自动<b><font color="#d32f2f">挂载到 Pod 目录 /run/secrets/kubernetes.io/serviceaccount 中</font></b>
bootstrap.kubernetes.io/token
用于节点接入集群的校验的 Secret
Opaque
创建Secret
Opaque 类型的数据是一个 map 类型,要求 value 必须是 base64 编码格式<br>
# 配置文件 链接<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/cm-test-pod.yaml<br>
使用方式
以环境变量的形式
此处也有两种方式挂载,一个是env,一个是envFrom,使用跟cm类似,不做过多说明
使用envFrom会直接挂载secret的data中key value名字到环境变量中
环境变量配置开始,使用env 可以自定义pod内环境变量名称
# 配置文件链接地址<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/secret1-pod.yaml<br>
以Volume的形式挂载
这样挂载容器中会有2文件,文件名=key名,文件内容等于secret key对应的value<br><br># 配置文件 链接<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-yaml/secret2-pod.yaml<br>
如果想要挂载到指定的文件上面,就使用items,指定key跟path
通过查看日志看结果<br>kubectl logs secret2-pod<br>
kubernetes.io/dockerconfigjson
创建用户 docker registry 认证的 Secret
$ kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL<br>
通过指定文件的方式来创建镜像仓库认证信息,需要注意对应的 KEY 和 TYPE
$ kubectl create secret generic myregistry --from-file=.dockerconfigjson=/root/.docker/config.json --type=kubernetes.io/dockerconfigjson
如果我们需要拉取私有仓库中的 Docker 镜像的话就需要使用到上面的 myregistry 这个
apiVersion: v1<br>kind: Pod<br>metadata:<br> name: foo<br>spec:<br> containers:<br> - name: foo<br> image: 192.168.1.100:5000/test:v1<br> <b>imagePullSecrets:<br> - name: myregistry</b>
注意:ImagePullSecrets 与 Secrets 不同,因为 Secrets 可以挂载到 Pod 中,但是 ImagePullSecrets 只能由 Kubelet 访问
除了设置 Pod.spec.imagePullSecrets 这种方式来获取私有镜像之外,我们还可以通过在 ServiceAccount 中设置 imagePullSecrets,然后就会自动为使用该 SA 的 Pod 注入 imagePullSecrets 信息:<br>
apiVersion: v1<br>kind: ServiceAccount<br>metadata:<br> name: default<br> namespace: default<br>secrets:<br>- name: default-token-5tsh4<br>imagePullSecrets:<br>- name: myregistry
kubernetes.io/service-account-token
另外一种 Secret 类型就是 kubernetes.io/service-account-token,用于被 ServiceAccount 引用。ServiceAccout 创建时 Kubernetes 会默认创建对应的 Secret。Pod 如果使用了 ServiceAccount,对应的 Secret 会自动挂载到 Pod 的 /var/run/secrets/kubernetes.io/serviceaccount/ 目录中
# 创建一个Pod<br>kubectl run secret-pod3 --image nginx:1.7.9<br>
验证上面说的结果
[root@master01 pod-secret]# kubectl get po secret-pod3 -oyaml | grep -A 3 volumeMounts<br> volumeMounts:<br> - mountPath: /var/run/secrets/kubernetes.io/serviceaccount<br> name: default-token-2wfgq<br> readOnly: true
可以看到默认把名为 default(自动创建的)的 ServiceAccount 对应的 Secret 对象通过 Volume 挂载到了容器的 /var/run/secrets/kubernetes.io/serviceaccount 的目录中
使用注意
同样 Secret 文件大小限制为 1MB(ETCD 的要求);Secret 虽然采用 Base64 编码,但是我们还是可以很方便解码获取到原始信息,所以对于非常重要的数据还是需要慎重考虑,可以考虑使用 Vault(https://www.vaultproject.io/) 来进行加密管理
Secret vs ConfigMap
相同点
key/value的形式
属于某个特定的命名空间<br>
可以导出到环境变量
可以通过目录/文件形式挂载
通过 volume 挂载的配置信息均可热更新
不同点
Secret 可以被 ServerAccount 关联
Secret 可以存储 docker register 的鉴权信息,用在 ImagePullSecret 参数中,用于拉取私有仓库的镜像
Secret 支持 Base64 加密
Secret 分为 kubernetes.io/service-account-token、kubernetes.io/dockerconfigjson、Opaque 三种类型,而 Configmap 不区分类
ServiceAccount
ServiceAccount是干什么的?
ServiceAccount 主要是用于解决 Pod 在集群中的身份认证问题的<br>认证使用的授权信息其实就是类型为 kubernetes.io/service-account-token 进行管理的<br>
ServiceAccount 是命名空间级别的,每一个命名空间创建的时候就会自动创建一个名为 default 的 ServiceAccount 对象
# 详情看链接<br>https://github.com/tzh666/k8s-yaml/blob/main/ServiceAccount/ServiceAccount.md<br>
在容器中获取Pod的信息(Download API)
在Pod创建成功以后, 会为Pod和容器设置一些额外的信息,例如Pod级别的 Pod名称、IP、Label、容器级别的资源限制等。<br><br>为了在Pod中能够使用到这些信息,所以k8s提供了一个Downward API机制,可以将这些原数据注入到容器中
有两种方式可以将Pod和容器的元数据注入到容器内部
环境变量:将Pod或Container信息设置为容器内的环境变量
Volume挂载:将Pod或者Container信息以文件的形式挂载到容器内部
环境变量
将Pod信息设置为容器内的环境变量
注意: 环境变量不能直接设置value,而是要设置valueFrom.fieldRef.fieldPath对Pod的元素据进行引用
# 在线配置文件地址<br>https://github.com/tzh666/k8s-yaml/blob/main/downward-API/<br>
将Container信息设置为容器内的环境变量
注意: 环境变量不能直接设置value,而是要设置valueFrom.resourceFieldRef对Pod的元素据进行引用<br>
# 在线配置文件地址<br>https://github.com/tzh666/k8s-yaml/blob/main/downward-API/
Volume挂载
将Pod信息设置为容器内的文件
# 在线配置文件地址<br>https://github.com/tzh666/k8s-yaml/blob/main/downward-API/
将Container信息设置为容器内的文件
# 在线配置文件地址<br>https://github.com/tzh666/k8s-yaml/blob/main/downward-API/
Pod的生命周期与重启策略
生命周期
Pod在整个生命周期中被定义为各种状态
Pending
API Server 已经创建该Pod,但是Pod内还有一个或者多个容器没有创建,包括正在下载镜像的过程
Running
Pod内所有容器均已创建,且至少有一个容器处于运行状态、正在启动状态或者正常重启状态
Succeeded
Pod内的所有容器均已成功执行后退出,且不会再重启
Failed
Pod内所有容器均已退出,但是至少有一个容器为退出状态
Unknown
由于某种原因无法获取该Pod的状态,可能是网络通信不畅导致
重启策略
Always
当容器失效时,自动重启该容器----默认是这个
<b>运用场景:RC和DS必须设置为Always,需要保证该容器持续运行</b>
OnFailure
当容器终止运行,且退出的代码不为0时,由kubelet 自动重启该容器
<b>运用场景:Job可以设置为OnFailure或Never,确保容器执行完成后不再重启</b>
Never
不管容器是什么运行状态,kubelet都不会重启该容器
<b>运用场景:kubelet管理的静态Pod,在Pod失效时也不去重启它</b>
Pod的健康检查和服务可用性检测
<b>注意:1、</b>三种检查方式同时只能使用一种
Liveness Probe(存活探针)
作用:同于判断容器是否存活状态(Running状态)
如果LivenessProbe探针探测到容器不健康,则kubelet将干掉该容器,并根据容器的重启策略做出相应的处理
注意:如果一个容器不包含LivenessProbe探针,那么kubelet认为该容器的LivenessProbe探针返回值永远是Success
Readiness Probe(可读性探针)
作用:用于判断容器是否可用(Ready状态),达到Ready状态的Pod才可以接收请求
1、应被SVC管理的Pod,svc与Pod ep管理关系也将基于Pod是否Ready进行设置<br>2、如果在运行过程中Ready变成False,则系统自动将其从Service的后端ep列表中隔离出去,后续再把Ready状态的Pod加回到Ep列表<br>3、这样就能保证客户端在访问SVC时不会被转发到服务不可用的Pod实例上
<b>需要注意的是:可读写探针是定期触发的,存在于Pod的整个生命周期中</b>
Startup Probe(启动探针)
<b>作用:同于应用启动比较慢的情况,例如应用程序启动时,需与远程服务器建立网络连接,或者遇到网络访问比较慢等情况时,会造成容器启动缓慢,</b>
<b>此时可读性探针就不适用了,因为这是属于有且仅有一次的超长延迟。所以此时需要用启动探针解决该问题</b>
探针配置方式(以上探针均可实现这三种方式):
ExecAction:在容器内部运行一个命令,如果这个命令返回值为0,则表明容器健康<br><br># 在线配置文件 https://github.com/tzh666/k8s-yaml/blob/main/pod-probe/probe-exec.yaml<br>
# 注意我们这里设置第一次检查时间是15s,而rm -rf /tmp/health是在容器启动10s后删除的,也就说15后容器会自动重启<br> # [root@master01 pod-probe]# kubectl describe po liveness-exec | grep Exit <br> # Exit Code: 137<br> # 通过看Exit Code发现状态码是137,也就是文件不存在,所以是正常的,因为10s后,执行了rm -rf /tmp/health<br> # 那么想让容器不重启,改initialDelaySeconds<10,或者启动命令的sleep改>15即可
timeoutSeconds:探测超时时间,默认1秒,最小1秒。<br>successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1,但是如果是liveness则必须是 1。最小值是 1。<br>failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3,最小值是 1
TCPSocketAction:通过容器的IP 和端口执行TSP检查,如果能建立TCP连接,则表明容器健康<br><br># 在线配置文件 https://github.com/tzh666/k8s-yaml/blob/main/pod-probe/probe-tcp.yaml<br>
HTTPGetAction:通过容器的IP、Port以及路径调用HTTP Get方法,如果相应的状态码大于等于200且小于400,则任务容器健康<br><br># 在线配置文件 https://github.com/tzh666/k8s-yaml/blob/main/pod-probe/probe-http.yaml
Pod Hook
Pod Hool是什么?
1、<b>Kubernetes 为我们的容器提供了生命周期的钩子,就是我们说的Pod Hook,</b><br>2、Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。<br>3、我们可以同时为 Pod 中的所有容器都配置 hook。<br>
Pod Hook 分类
PostStart
<b>这个钩子在容器创建后立即执行</b>,但是,并不能保证钩子将在容器 ENTRYPOINT 之前运行,因为没有参数传递给处理程序
<b>用途:主要用于资源部署、环境准备等</b>
<b>注意:如果钩子花费太长时间以至于不能运行或者挂起,容器将不能达到 running 状态</b>
PreStop
这个钩子在容器终止之前立即被调用,它是阻塞的,意味着它是同步的,所以它必须在删除容器的调用发出之前完成
<b>用途:要用于优雅关闭应用程序、通知其他系统等</b>
注意:如果钩子在执行期间挂起,Pod 阶段将停留在 running 状态并且永不会达到 failed 状态<br>
<b>注意:如果 PostStart 或者 PreStop 钩子失败, 它会杀死容器。所以我们应该让钩子函数尽可能的轻量。<br>当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。</b><br>
有两种方式来实现上面的钩子函数
Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器
HTTP - 对容器上的特定的端点执行 HTTP 请求
# 在线案例,启动之前做些事情<br>https://github.com/tzh666/k8s-yaml/pod-hook<br>
# 在线案例,一个是利用 preStop 来进行优雅删除,另外一个是利用 preStop 来做一些信息记录的事情<br>https://github.com/tzh666/k8s-yaml/pod-hook<br>
# 创建完成后,我们可以直接删除 hook-demo2 这个 Pod,在容器删除之前会执行 preStop 里面的优雅关闭命令<br># 这个用法在后面我们的滚动更新的时候用来保证我们的应用零宕机非常有用<br># 第二个 Pod 我们声明了一个 hostPath 类型的 Volume,在容器里面声明挂载到了这个 Volume,所以当我们删除 Pod,退出容器之前,在容器里面输出的信息也会同样的保存到宿主机(一定要是 Pod 被调度到的目标节点)的 /tmp 目录下面
K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S 提供两种信息通知
默认:K8S 通知 node 执行 docker stop 命令,<b>docker 会先向容器中 PID 为 1 的进程发送系统信号SIGTERM</b>,<br>然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行 kill 掉进程<br>
使用 Pod 生命周期(利用PreStop回调函数),它在发送终止信号之前执行
默认所有的优雅退出时间都在30秒内。kubectl delete 命令支持 --grace-period=<seconds>选项,这个选项允许用户用他们自己指定的值覆盖默认值。值'0'代表强制删除 pod。 在 kubectl 1.5 及以上的版本里,执行强制删除时必须同时指定 --force --grace-period=0
强制删除一个 pod 是从集群状态还有 etcd 里立刻删除这个 pod,只是当 Pod 被强制删除时, APIServer 不会等待来自 Pod 所在节点上的 kubelet 的确认信息:pod 已经被终止。在 API 里 pod 会被立刻删除,在节点上, pods 被设置成立刻终止后,在强行杀掉前还会有一个很小的宽限期
Pod 资源配置
CGroup 里面对于 CPU 资源的单位换算
# m 就是毫、毫核的意思<br>1 CPU = 1000 millicpu(1 Core = 1000m)<br><br>0.5 CPU = 500 millicpu (0.5 Core = 500m)<br>
spec.containers[].resources.limits.cpu:CPU 上限值,可以短暂超过,容器也不会被停止<br>spec.containers[].resources.requests.cpu:CPU请求值,Kubernetes 调度算法里的依据值,可以超过
内存的单位换算
1 MiB = 1024 KiB,内存这块在 Kubernetes 里一般用的是Mi单位,当然你也可以使用Ki、Gi甚至Pi,看具体的业务需求和资源容量
MiB ≠ MB,MB 是十进制单位,MiB 是二进制,平时我们以为 MB 等于 1024KB,其实1MB=1000KB,1MiB才等于1024KiB。中间带字母 i 的是国际电工协会(IEC)定的,走1024乘积;KB、MB、GB是国际单位制,走1000乘积
内存是不可压缩性资源,如果容器使用内存资源到达了上限,那么会OOM,造成内存溢出,容器就会终止和退出
Pod调度
nodeSelector
<b># nodeSelector是通过节点label标签来定向调度,如果标签不存在那么Pod就会Pending</b>
# 通过基于Node标签的·调度方式,我们可以把集群中具有不同特点的Node都贴上不同的标签(相同的就贴上相同的...),<br>例如 "role=tzh"等,那么这个时候部署服务的时候通过nodeSelector就可以来指定Node范围调度
# 查看节点标签<br>1、kubectl get nodes --show-labels<br>
# 2、给node02打标签<br>[root@master01 k8s-yaml]# kubectl label nodes node02 com=tzh<br>node/node02 labeled<br>
# 3、编写配置文件调度pod<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-manage/node-selector.yaml<br>
亲和性和反亲和性调度
亲和性调度可以分成软策略和硬策略两种方式
对于亲和性和反亲和性都有这两种规则可以设置<b>,且这两种规则可以同时配置,先硬后软</b>
软策略<br>preferredDuringSchedulingIgnoredDuringExecution<br>
软策略就是如果现在没有满足调度要求的节点的话,Pod 就会忽略这条规则,继续完成调度过程
说白了就是满足条件最好了,没有的话也无所谓
硬策略<br>requiredDuringSchedulingIgnoredDuringExecution<br>
硬策略就比较强硬了,如果没有满足条件的节点的话,就不断重试直到满足条件为止
简单说就是你必须满足我的要求,不然就不干了
Kubernetes 提供的操作符
In:label 的值在某个列表中
NotIn:label 的值不在某个列表中
Gt:label 的值大于某个值
Gt:label 的值大于某个值
Exists:某个 label 存在
DoesNotExist:某个 label 不存在
节点亲和性(nodeAffinity)
1、节点亲和性(nodeAffinity)主要是用来控制 Pod 要部署在哪些节点上,以及不能部署在哪些节点上的,<br>2、它可以进行一些简单的逻辑组合了,不只是简单的相等匹配<br>3、举例说明: Pod 首先是要求不能运行在 master01 这个节点上,如果有个节点满足 com=tzh 的话就优先调度到这个节点上<br>4、打标签:kubectl label nodes node01 com=tzh<br>5、配置文件: https://github.com/tzh666/k8s-yaml/blob/main/pod-manage/node-affinity.yaml
<b>注意:<br>1、如果 nodeSelectorTerms 下面有多个选项的话,满足任何一个条件就可以了;<br>2、如果 matchExpressions有多个选项的话,则必须同时满足这些条件才能正常调度 Pod</b><br>
Pod 亲和性(podAffinity)
1、Pod 亲和性(podAffinity)主要解决 Pod 可以和哪些 Pod 部署在同一个拓扑域中的问题<br>2、由于我们这里只有一个集群,并没有区域或者机房的概念,所以我们这里直接使用主机名来作为拓扑域,把 Pod 创建在同一个主机上面<br>3、这种方式需要有一个Pod A调度到某个节点B上,然后再通过Pod亲和性,让后这个Pod C调度到Pod A所在的B节点上<br>4、https://github.com/tzh666/k8s-yaml/blob/main/pod-manage/pod-affinity.yaml
<b>其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等</b>
Pod 反亲和性()podAntiAffinity
Pod 反亲和性主要是解决 Pod 不能和哪些 Pod 部署在同一个拓扑域中的问题<br>
Pod 反亲和性(podAntiAffinity)则是反着来的,比如一个节点上运行了某个 Pod,那么我们的模板 Pod 则不希望被调度到这个节点上面去了。我们把上面的 podAffinity 直接改成 podAntiAffinity<br><br>https://github.com/tzh666/k8s-yaml/blob/main/pod-manage/podAntiAffinity.yaml<br>
<b>其中拓扑域用主机标签实现,可以是单个主机,也可以是多个主机组成的 cluster、zone 等等</b>
污点与容忍
污点(Taints)
如果一个节点标记为 Taints ,除非 Pod 也被标识为可以容忍污点节点,否则该 Taints 节点不会被调度 Pod<br>
一个节点可以存在多个Taints,一个Pod也可以设置多个tolerations
污点 操作
# 查看污点<br>kubectl describe node master01 | grep Ta<br>
# 打污点<br>kubectl taint no node04 key=value:NoSchedule<br>
# 删污点(打污点的命令后面跟个 - 即可)<br>kubectl taint no node04 key=value:NoSchedule-<br>
污点类型
NoSchedule
不能调度到污点节点上去
PreferNoSchedule
NoSchedule 的软策略版本,表示尽量不调度到污点节点上去
NoExecute
该选项意味着一旦 Taint 生效,如该节点内正在运行的 Pod 没有对应容忍(Tolerate)设置,则会直接被逐出
容忍(tolerations)
容忍,就是可以让Pod调度到有污点的节点上去,在Pod级别通过关键字tolerations
# 例子,上面我们给node04打了污点,我们可以通过以下设置,使得Pod也能调度过去
tolerations:<br>- key: "key"<br> operator: "Equal"<br> value: "value"<br> effect: "NoSchedule"<br><br>--- <br># 或者<br>tolerations:<br>- key: "key"<br> operator: "Exists"<br> effect: "NoSchedule"
对于 tolerations 属性的写法,其中的 key、value、effect 与 Node 的 Taint 设置需保持一致
如果 operator 的值是 Exists,则 value 属性可省略
如果 operator 的值是 Equal,则表示其 key 与 value 之间的关系是 equal(等于)
如果不指定 operator 属性,则默认值为 Equal
另外,还有两个特殊值
空的 key 如果再配合 Exists 就能匹配所有的 key 与 value,也就是是能容忍所有节点的所有 Taints
空的 effect 匹配所有的 effect
调度流程
调度要考虑的东西
如何保证全部的节点调度的公平性?要知道并不是所有节点资源配置一定都是一样的
如何保证每个节点都能被分配资源?
集群资源如何能够被高效利用?
集群资源如何才能被最大化使用?
如何保证 Pod 调度的性能和效率?
用户是否可以根据自己的实际需求定制自己的调度策略?
调度主要分为以下几个部分
首先是预选过程,过滤掉不满足条件的节点,这个过程称为 Predicates(过滤)
Predicates 阶段首先遍历全部节点,过滤掉不满足条件的节点,属于强制性规则,这一阶段输出的所有满足要求的节点将被记录并作为第二阶段的输入,如果所有的节点都不满足条件,那么 Pod 将会一直处于 Pending 状态,直到有节点满足条件,在这期间调度器会不断的重试
所以我们在部署应用的时候,如果发现有 Pod 一直处于 Pending 状态,那么就是没有满足调度条件的节点,这个时候可以去检查下节点资源是否可用。<br>
然后是优选过程,对通过的节点按照优先级排序,称之为 Priorities(打分)
Priorities 阶段即再次对节点进行筛选,如果有多个节点都满足条件的话,那么系统会按照节点的优先级(priorites)大小对节点进行排序,最后选择优先级最高的节点来部署 Pod 应用
最后从中选择优先级最高的节点,如果中间任何一步骤有错误,就直接返回错误
更详细的流程是这样的
首先,客户端通过 API Server 的 REST API 或者 kubectl 工具创建 Pod 资源<br>API Server 收到用户请求后,存储相关数据到 etcd 数据库中<br>调度器监听 API Server 查看到还未被调度(bind)的 Pod 列表,循环遍历地为每个 Pod 尝试分配节点,这个分配过程就是我们上面提到的两个阶段:<br><br>预选阶段(Predicates),过滤节点,调度器用一组规则过滤掉不符合要求的 Node 节点,比如 Pod 设置了资源的 request,那么可用资源比 Pod 需要的资源少的主机显然就会被过滤掉<br>优选阶段(Priorities),为节点的优先级打分,将上一阶段过滤出来的 Node 列表进行打分,调度器会考虑一些整体的优化策略,比如把 Deployment 控制的多个 Pod 副本尽量分布到不同的主机上,使用最低负载的主机等等策略<br>经过上面的阶段过滤后选择打分最高的 Node 节点和 Pod 进行 binding 操作,然后将结果存储到 etcd 中 最后被选择出来的 Node 节点对应的 kubelet 去执行创建 Pod 的相关操作(当然也是 watch APIServer 发现的)。
调度器调优--percentageOfNodesToScore
在 Kubernetes 1.12 版本之前,kube-scheduler 会检查集群中所有节点的可调度性,并且给可调度节点打分
版本大于 1.12
1、Kubernetes 1.12 版本添加了一个新的功能,允许调度器在找到一定数量的可调度节点之后就停止继续寻找可调度节点。<br>2、该功能能提高调度器在大规模集群下的调度性能,这个数值是集群规模的百分比,这个百分比通过 percentageOfNodesToScore 参数来进行配置<br>3、其值的范围在 1 到 100 之间,最大值就是 100%,如果设置为 0 就代表没有提供这个参数配置
版本大于1.14
Kubernetes 1.14 版本又加入了一个特性,在该参数没有被用户配置的情况下,调度器会根据集群的规模自动设置一个集群比例,然后通过这个比例筛选一定数量的可调度节点进入打分阶段。该特性使用线性公式计算出集群比例,比如100个节点的集群下会取 50%,在 5000节点的集群下取 10%,这个自动设置的参数的最低值是 5%,换句话说,调度器至少会对集群中 5% 的节点进行打分,除非用户将该参数设置的低于 5。
注意
当集群中的可调度节点少于 50 个时,调度器仍然会去检查所有节点,因为可调度节点太少,不足以停止调度器最初的过滤选择。如果我们想要关掉这个范围参数,可以将 percentageOfNodesToScore 值设置成 100
大于1000个节点这参数才明显的展示效果
1、percentageOfNodesToScore 的值必须在1到 100之间,而且其默认值是通过集群的规模计算得来的,另外 50 个 Node 的数值是硬编码在程序里面的,<br>2、设置这个值的作用在于:当集群的规模是数百个节点并且 percentageOfNodesToScore 参数设置的过低的时候,调度器筛选到的可调度节点数目基本不会受到该参数影响。当集群规模较小时,这个设置对调度器性能提升并不明显,但是在超过 1000 个 Node 的集群中,将调优参数设置为一个较低的值可以很明显的提升调度器性能<br>
如果你的集群规模只有数百个节点或者更少,实际上并不推荐你将这个参数设置得比默认值更低,因为这种情况下不太会有效的提高调度器性能
优先级调度
此处的优先级调度
与上面所讲的调度优选策略中的优先级(Priorities)不同,前面所讲的优先级指的是节点优先级
而我们这里所说的优先级指的是 Pod 的优先级,<b>高优先级的 Pod 会优先被调度</b>
<b>或者在资源不足低情况牺牲低优先级的 Pod</b>,以便于重要的 Pod 能够得到资源部署
Pod优先级定义
要定义 Pod 优先级,就需要先定义 PriorityClass 对象,<b>该对象没有 Namespace 的限制</b>
# 查看默认的PriorityClass<br>kubectl get priorityclasses<br>
创建PriorityClass对象
# 资源清单<br>https://github.com/tzh666/k8s-yaml/blob/main/priorityclass/pod-prioritycass.yaml<br>
注意
value 为 32 位整数的优先级,该值越大,优先级越高
globalDefault 用于未配置 PriorityClassName 的 Pod,整个集群中应该只有一个 PriorityClass 将其设置为 true
该对象没有 Namespace 的限制
使用这个对象
在 Pod 的 spec.priorityClassName 中指定已定义的 PriorityClass 名称即可
# 资源清单<br>https://github.com/tzh666/k8s-yaml/blob/main/priorityclass/use-priority.yaml<br>
初始化容器
应用场景
等待其他关联组件正确运行(例如数据库、某个后台服务、数据初始化)
基于环境变量或者配置模板生成配置文件
从远程数据库获取本地所需配置,或者讲自身服务注册到某个中央数据库中、配置中心等(nacos)
下载相关的依赖包,或者对系统进行有一些预配置操作
比如现在我们来实现一个功能,在 Nginx Pod 启动之前去重新初始化首页内容<br>https://github.com/tzh666/k8s-yaml/blob/main/init-pod/init-pod.yaml<br>
init 容器跟应用容器的区别
运行方式不同
init容器必须先于应用容器执行完成,有多个init容器,将按顺序逐个运行
当最后一个init容器运行成功,也就是全部init容器运行成功,k8s才会初始化Pod的各种信息,并开始创建和运行应用容器
资源限制不同
init 容器也可以设置资源限制、volume、安全策略等
如果多个init都定义了资源请求、资源限制,那么取最大的值作为所有的init容器的有效请求资源值、资源限制值
Pod的有效(effect)资源请求值/限制资源请求值取以下较大值
所有的应用容器的资源请求值/资源限制值之和
init容器的有效资源请求值/资源限制值
<b>说白了就是,Pod的资源限制、请求取决于:如果应用容器的限制值的和大于有效init容器的资源值,则取应用容器的资源限制值,反值取init的有效请求值</b>
调度算法也会基于Pod的init 容器有效请求、限制值去调度
Pod的QoS等级适用于应用容器、init容器<br>
Pod级别的cgroup将基于Pod的有效资源请求、限制,于调度机制一致
探针限制不同
init容器不能设置readinessProbe探针,因为这个探针必须设置在运行在Pod中定义的普通容器
Pod常见的重启场景
init 容器的镜像被更新时,init容器将会重启,那么Pod也会重启。仅更新应用容器镜像,只会使得应用容器被重启,init容器不会
Pod的infrastructure容器更新时,Pod将会被重启,基础容器就是pause那个
控制器<br>
在k8s中控制器怎么理解?
Kubernetes 控制器会监听资源的 创建/更新/删除 事件,并触发 Reconcile 函数作为响应
整个调整过程被称作 “Reconcile Loop”(调谐循环) 或者 “Sync Loop”(同步循环)<br>
Reconcile 是一个使用资源对象的命名空间和资源对象名称来调用的函数,使得资源对象的实际状态与 资源清单中定义的状态保持一致。调用完成后,Reconcile 会将资源对象的状态更新为当前实际状态
基本上不直接用这两个
ReplicaSet 控制器
<b>ReplicaSet(RS) 的主要作用</b>
<b>维持一组 Pod 副本的运行,保证一定数量的 Pod 在集群中正常运行</b>,ReplicaSet 控制器会持续监听它所控制的这些 Pod 的运行状态,<br>在 Pod 发送故障数量减少或者增加时会触发调谐过程,始终保持副本数量一定<br>
# ReplicaSet 控制器会通过定义的 Label Selector 标签去查找集群中的 Pod 对象
Replication 控制器
Replication Controller 简称 RC,实际上 RC 和 RS 的功能几乎一致,RS 算是对 RC 的改进,目前唯一的一个区别就是 RC 只支持基于等式的 selector(env=dev或environment!=qa),但 RS 还支持基于集合的 selector(version in (v1.0, v2.0))
RC 只支持单个 Label 的等式,而 RS 中的 Label Selector 支持 matchLabels 和 matchExpressions 两种形式:
Deployment控制器
亮点
Deployment 最突出的一个功能是支持滚动更新
<b>用于部署无状态的服务</b>
<b>可以管理多个副本的Pod实现无缝迁移、自动扩容缩容、自动灾难恢复、一键回滚等功能</b>
比如我们应用更新了,我们只需要更新我们的容器镜像,然后修改 Deployment 里面的 Pod 模板镜像,那么 Deployment 就会用滚动更新(Rolling Update)的方式来升级现在的 Pod
因为对于线上的服务我们需要做到不中断服务,所以滚动更新就成了必须的一个功能
deployment的水平伸缩
# 增加副本数<br>kubectl scale deployment nginx-deploy --replicas=4<br><br># 查看扩容结果<br>kubectl get rs<br><br># 减少副本数<br>kubectl scale deployment nginx-deploy --replicas=2<br>
deployment的滚动更新
Deployment&nbsp;最突出的一个功能是支持滚动更新<br>
滚动更新策略
RollingUpdate(默认)
滚动更新,更新了一个Pod,再更新第二个
Recreate
重建,先删除旧的Pod,在创建新的Pod
更新策略参数说明
minReadySeconds
可选参数,指定<b>新创建的Pod在没有任何容器崩溃的情况下视为Ready最小的秒数</b>,默认为0,即一旦被创建就视为可用
如果没有设置该值,Kubernetes 会假设该容器启动起来后就提供服务了,<br>如果没有设置该值,在某些极端情况下可能会造成服务不正常运行<br>
maxSurge
表示升级过程中最多可以比原先设置多出的 Pod 数量,例如:maxSurage=1,replicas=5,就表示Kubernetes 会先启动一个新的 Pod,然后才删掉一个旧的 Pod,整个升级过程中最多会有5+1个 Pod
maxUnavaible
表示升级过程中最多有多少个 Pod 处于无法提供服务的状态,当maxSurge不为0时,该值也不能为0,例如:maxUnavaible=1,则表示 Kubernetes 整个升级过程中最多会有1个 Pod 处于无法服务的状态
.spec.revisionHistoryLimit
设置保留RS旧的revision的个数,设置为0的话,不保留历史数据,新版本中该值默认为 10
滚动更新命令演示
创建一个deploy资源清单<br>kubectl apply -f https://github.com/tzh666/k8s-yaml/blob/main/pod-controllers/deployment/nginx-deploy-strategy.yaml
record 参数
可以添加了一个额外的 --record 参数来记录下我们的每次操作所执行的命令,以方便后面查看
1、修改配置文件的image版本,然后再次apply -f 一次配置文件(这样就可以观察滚动更新)<br>image: nginx:1.7.9----->image<br><br><b># 当然也可以直接在命令行修改,cicd就是这样改<br>[root@master01 k8s-yaml]# kubectl set image deployment/nginx-deploy nginx=nginx:1.9.1<br>deployment.apps/nginx-deploy image updated<br><br></b># 或者edit在线更改<br><br>2、在打开一个窗口查看更新动态<br>watch -d kubectl get po<br><br>3、或者使用命令查看<br>kubectl rollout status deployment/nginx-deploy<br>
暂停更新(不常用)
kubectl rollout pause deployment/nginx-deploy
恢复滚动更新
kubectl rollout resume deployment/nginx-deploy
版本回滚
查看历史版本
[root@master01 k8s-yaml]# kubectl rollout history deployment nginx-deploy<br>deployment.apps/nginx-deploy <br>REVISION CHANGE-CAUSE<br>3 <none><br>4 <none>
rollout history 中记录的 revision 是和 ReplicaSets 一一对应
不过需要注意的是回滚的操作滚动的revision始终是递增的
查看一个revison的详细信息
kubectl rollout history deployment nginx-deploy --revision=4
回退到当前版本的前一个版本(上个版本)
kubectl rollout undo deployment nginx-deploy
回退到指定的revision版本
kubectl rollout undo deployment nginx-deploy --to-revision=1
回滚的过程中我们同样可以查看回滚状态
kubectl rollout status deployment/nginx-deploy
StatefulSet控制器
使用者sts控制器的时候,需要明白两个概念:什么是有状态服务,什么是无状态服务
无状态服务(Stateless Service)
<b>该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的</b>
有状态服务(Stateful Service)
<b>该服务运行的实例需要在本地存储持久化数据</b>
比如我们常见的 WEB 应用,是通过 Session 来保持用户的登录状态的,如果我们将 Session 持久化到节点上,那么该应用就是一个有状态的服务
statefulSet的功能特性
稳定的、唯一的网络标识符
稳定的、持久化的存储
有序的、优雅的部署和缩放
有序的、优雅的删除和终止
有序的、自动滚动更新
StatefulSet 中 Pod 副本的创建会按照序列号升序处理,副本的更新和删除会按照序列号降序处理。<br>
要使用statefulSet之前,我们还需要了解一个概念:Headless Service
1、我们都知道Service 是应用服务的抽象,通过 Labels 为应用提供负载均衡和服务发现,每个 Service 都会自动分配一个 cluster IP 和 DNS 名,在集群内部我们可以通过该地址或者通过 FDQN 的形式来访问服务
2、一个 Deployment 有 3 个 Pod,那么我就可以定义一个 Service,有如下两种方式来访问这个 Service
cluster IP 的方式
比如:当我访问 10.109.169.155 这个 Service 的 IP 地址时,10.109.169.155 其实就是一个 VIP,它会把请求转发到该 Service 所代理的 Endpoints 列表中的某一个 Pod 上
Service 的 DNS 方式
比如我们访问“mysvc.mynamespace.svc.cluster.local”这条 DNS 记录,就可以访问到 mynamespace 这个命名空间下面名为 mysvc 的 Service 所代理的某一个 Pod
3、对于 DNS 这种方式实际上也有两种情况
普通的 Service,
我们访问“mysvc.mynamespace.svc.cluster.local”的时候是通过集群中的 DNS 服务解析到的 mysvc 这个 Service 的 cluster IP 的
特殊的svc,没有vip,也就是Headless Service
对于这种情况,我们访问“mysvc.mynamespace.svc.cluster.local”的时候是直接解析到的 mysvc 代理的某一个具体的 Pod 的 IP 地址,中间少了 cluster IP 的转发,<b>这就是二者的最大区别</b>
Headless Service 不需要分配一个 VIP,而是可以直接以 DNS 的记录方式解析到后面的 Pod 的 IP 地址
定义一个如下的 Headless Service
在定义上和普通的 Service 几乎一致, 只是他的 clusterIP=None
apiVersion: v1<br>kind: Service<br>metadata:<br> name: nginx<br> namespace: default<br> labels:<br> app: nginx<br>spec:<br> ports:<br> - name: http<br> port: 80<br> clusterIP: None<br> selector:<br> app: nginx
对于 Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:<br><pod-name>.<svc-name>.<namespace>.svc.cluster.local<br>
DNS 记录正是 Kubernetes 集群为 Pod 分配的一个唯一标识,只要我们知道 Pod 的名字,以及它对应的 Service 名字,就可以组装出这样一条 DNS 记录访问到 Pod 的 IP 地址
创建一个StatefulSet对象
由于我们这里用volumeClaimTemplates声明的模板是挂载点的方式,并不是 volume,<br>所有实际上上当于把 PV 的存储挂载到容器中,所以会覆盖掉容器中的数据,<br>在容器启动完成后我们可以手动在 PV 的存储里面新建 index.html 文件来保证容器的正常访问,<br>当然也可以进入到容器中去创建,这样更加方便:<br><br>$ for i in 0 1; do kubectl exec web-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done<br>
# 在线配置文件<br>https://github.com/tzh666/k8s-yaml/tree/main/pod-controllers/StatefulSet<br>
<b>解析Headless service的名称</b>
kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh<br><br>nslookup nginx<br>
注意:<b>我们直接解析 Headless Service 的名称,可以看到得到的是两个 Pod 的解析记录</b>,<b>但实际上如果我们通过nginx这个 DNS 去访问我们的服务的话,并不会随机或者轮询背后的两个 Pod,而是访问到一个固定的 Pod</b>,所以不能代替普通的 Service
解析web-0.nginx
解析 web-0.nginx 的时候解析到了 web-0 这个 Pod 的 IP,<br>web-1.nginx 解析到了 web-1 这个 Pod 的 IP,<br>而且这个 DNS 地址还是稳定的,因为 Pod 名称就是固定的<br>
sts管理策略
对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的,这些系统仅仅要求唯一性和身份标志。为了解决这个问题,我们只需要在声明 StatefulSet 的时候重新设置 spec.podManagementPolicy 的策略即可
默认的管理策略是 OrderedReady,表示让 StatefulSet 控制器遵循上文演示的顺序性保证。除此之外,还可以设置为 Parallel 管理模式,表示让 StatefulSet 控制器并行的终止所有 Pod,在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态
sts更新策略
.spec.updateStrategy.type
OnDelete<br>该策略表示当更新了 StatefulSet 的模板后,只有手动删除旧的 Pod 才会创建新的 Pod<br>
RollingUpdate<br>该策略表示当更新 StatefulSet 模板后会自动删除旧的 Pod 并创建新的Pod,如果更新发生了错误,这次“滚动更新”就会停止<br>注意: StatefulSet 的 Pod 在部署时是顺序从 0~n 的,而在滚动更新时,这些 Pod 则是按逆序的方式即 n~0 一次删除并创建<br>
SatefulSet 的滚动升级支持 Partitions的特性
可以通过.spec.updateStrategy.rollingUpdate.partition 进行设置,在设置 partition 后,SatefulSet 的 Pod 中序号大于或等于 partition 的 Pod 会在 StatefulSet 的模板更新后进行滚动升级,而其余的 Pod 保持不
DaemonSet控制器
DaemonSet 作用
DaemonSet用来部署守护进程的,DaemonSet用于在每个 Kubernetes 节点中将守护进程的副本作为后台进程运行
说白了就是在每个节点部署一个 Pod副本,当节点加入到 Kubernetes 集群中,Pod 会被调度到该节点上运行,当节点从集群只能够被移除后,该节点上的这个 Pod 也会被移除
DaemonSet应用场景
集群存储守护程序,如 glusterd、ceph 要部署在每个节点上以提供持久性存储
节点监控守护进程,如 Prometheus 监控集群,可以在每个节点上运行一个 node-exporter 进程来收集监控节点的信息;
日志收集守护程序,如 fluentd 或 logstash,在每个节点上运行以收集容器的日志
节点网络插件,比如 flannel、calico,在每个节点上运行为 Pod 提供网络服务
DaemonSet调度
正常情况下,Pod 运行在哪个节点上是由 Kubernetes 的调度器策略来决定的,然而,由 DaemonSet 控制器创建的 Pod 实际上提前已经确定了在哪个节点上了(Pod创建时指定了.spec.nodeName)
DaemonSet 并不关心一个节点的 unshedulable 字段
DaemonSet 可以创建 Pod,即使调度器还没有启动
有污点不能调度,----后面补充
DaemonSet 控制器是如何保证每个 Node 上有且只有一个被管理的 Pod 呢?
首先控制器从 Etcd 获取到所有的 Node 列表,然后遍历所有的 Node
根据资源对象定义是否有调度相关的配置,然后分别检查 Node 是否符合要求
在可运行 Pod 的节点上检查是否已有对应的 Pod,如果没有,则在这个 Node 上创建该 Pod;如果有,并且数量大于 1,那就把多余的 Pod 从这个节点上删除;<b>如果有且只有一个 Pod,那就说明是正常情况</b>
更新策略
有OnDelete和RollingUpdate两种方式,默认是滚动更新
Job 与 CronJob
Job
什么是job?
Job 负责处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个 Pod 成功结束<br>
注意
1、Job 的 RestartPolicy 仅支持 Never 和 OnFailure 两种,不支持 Always<br>2、Job编排的Pod正常退出后的状态是Completed
3、如果定义了 restartPolicy=OnFailure,那么任务在执行失败后 Job 控制器就会不断地尝试创建一个新 Pod,<br>可以通过spec.backoffLimit 字段定义重试次数<br>
4、Job 控制器重新创建 Pod 的间隔是呈指数增加的,即下一次重新创建 Pod 的动作会分别发生在 10s、20s、40s… 后
5、可以在 Job 对象中通过设置字段 spec.activeDeadlineSeconds 来限制任务运行的最长时间
6、可以通过设置 spec.parallelism 参数来进行并行控制,该参数定义了一个 Job 在任意时间最多可以有多少个 Pod 同时运行
7、spec.completions 参数可以定义 Job 至少要完成的 Pod 数目
# 例子<br>https://github.com/tzh666/k8s-yaml/blob/main/Job%2BCronJob/job-demo.yaml<br>
# 查看创建的Job<br>kubectl get jobs job-demo<br><br># 查看对应的po<br>kubectl get po | grep jobname (job.metadata.name)<br>
# 查看下对象的详细描述信息<br>kubectl describe job job-demo<br>
1、可以看到,Job 对象在创建后,它的 Pod 模板,被自动加上了一个 controller-uid=< 一个随机字符串 > 这样的 Label 标签
2、而这个 Job 对象本身,则被自动加上了这个 Label 对应的 Selector,从而 保证了 Job 与它所管理的 Pod 之间的匹配关系
3、而 Job 控制器之所以要使用这种携带了 UID 的 Label,就是为了避免不同 Job 对象所管理的 Pod 发生重合
4、查看对应Pod的Label也是可以看到这个controlle-uid的<br>kubectl describe po job-demo-q47zv | grep Labels<br>
CronJob
CronJob 则就是在 Job 上加上了时间调度,可以在给定的时间点运行一个任务,也可以周期性地在给定时间点运行
一个 CronJob 对象其实就对应中 crontab 文件中的一行,它根据配置的时间格式周期性地运行一个Job,格式和 crontab 也是一样的
crontab 的格式为:分 时 日 月 星期 要运行的命令 。<br><br>第1列分钟0~59<br>第2列小时0~23)<br>第3列日1~31<br>第4列月1~12<br>第5列星期0~7(0和7表示星期天)<br>第6列要运行的命令
# 我们用 CronJob 来管理我们上面的 Job 任务<br>https://github.com/tzh666/k8s-yaml/blob/main/Job%2BCronJob/cronjob-demo.yaml<br>
.spec.successfulJobsHistoryLimit(默认为3) 和 .spec.failedJobsHistoryLimit(默认为1),表示历史限制,是可选的字段,指定可以保留多少完成和失败的 Job
# 可以查看对应的 Cronjob 资源对象<br>kubectl get cronjob<br>kubectl get cj<br><br># 查看对应起的Pod,这个Pod会根据CronJob的schedule设置的周期,定时起一个<br>kubectl get po | grep cronjob-demo (CronJob.metadata.name)<br>
# 删除Job,<b>注意:这将会终止正在创建的 Job,但是运行中的 Job 将不会被终止,不会删除 Job 或 它们的 Pod</b><br>kubectl delete cronjob cronjob-demo<br>
Job、CronJob、Pod的关系
1、CronJob编排好以后会定时起一个Job,CronJob跟Pod没直接关系
2、Job创建后describe可以看到k8s会自动打上一个Label标签(controller-uid=随机字符串),这才会跟Pod关联起来
HPA
HPA是什么?
Horizontal Pod Autoscaling(Pod 水平自动伸缩),简称HPA<br><br>原理:HPA 通过监控分析一些控制器控制的所有 Pod 的负载变化情况来确定是否需要调整 Pod 的副本数量
HPA是如何实现动态伸缩功能的?
<b>HPA Controller默认30s轮询一次</b>(可通过 kube-controller-manager 的-<b>-horizontal-pod-autoscaler-sync-period 参数进行设置</b>),<br>查询指定的资源中的 Pod 资源使用率,并且与创建时设定的值和指标做对比,从而实现自动伸缩的功能<br>
使用HPA前提
安装Metrices Server 前提
1、安装 Metrics Server 服务,安装 Metrics Server 需要开启 Aggregator<br>2、如果集群是通过 Kubeadm 搭建的,默认已经开启
3、如果是二进制方式安装的集群,需要单独配置 kube-apsierver 添加
--requestheader-client-ca-file=<path to aggregator CA cert><br>--requestheader-allowed-names=aggregator<br>--requestheader-extra-headers-prefix=X-Remote-Extra-<br>--requestheader-group-headers=X-Remote-Group<br>--requestheader-username-headers=X-Remote-User<br>--proxy-client-cert-file=<path to aggregator proxy cert><br>--proxy-client-key-file=<path to aggregator proxy key>
4、如果 kube-proxy 没有和 APIServer 运行在同一台主机上,那么需要确保启用了如下 kube-apsierver 的参数
--enable-aggregator-routing=true<br>
安装Metrices Server
1、wget https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.4.0/components.yaml
2、修改 components.yaml 的镜像地址<br>kubectl apply -f components.yaml<br>
hostNetwork: true # 使用hostNetwork模式<br>containers:<br>- name: metrics-server<br> image: cnych/metrics-server:v0.4.0
3、部署集群的时候,CA 证书并没有把各个节点的 IP 签上去,所以这里 Metrics Server 通过 IP 去请求时,提示签的证书没有对应的 IP(错误:x509: cannot validate certificate for 10.151.30.22 because it doesn’t contain any IP SANs),我们可以添加一个--kubelet-insecure-tls参数跳过证书校验:
args:<br>- --cert-dir=/tmp<br>- --secure-port=4443<br>- --kubelet-insecure-tls<br>- --kubelet-preferred-address-types=InternalIP
4、验证是否安装成功
kubectl top 命令来获取到资源数据了,证明 Metrics Server 已经安装成功
HPA演示-基于CPU
1、用deploye来创建一个nginx-pod。然后利用hpa来进行自动扩容<br>https://github.com/tzh666/k8s-yaml/blob/main/HPA/hpa-demo.yaml<br>
2、创建一个 HPA 资源对象,这个deployment对象,一定要做pod的资源限制,要不创建hap关联对象会报错(failed to get cpu utilization: missing request for cpu)<br>kubectl autoscale deployment hpa-demo --cpu-percent=10 --min=1 --max=10<br><br>命令说明:<br>1、此命令创建了一个关联资源 hpa-demo 的 HPA,最小的 Pod 副本数为1,最大为10<br>2、HPA 会根据设定的 cpu 使用率(10%)动态的增加或者减少 Pod 数量
3、hpa对象信息介绍<br>kubectl describe hpa hpa-demo<br>
通过Reference可以看到关联的资源 是hpa-demo
4、模拟访问pod,查看hpa是否会给我们扩容<br>kubectl run -it --image busybox test-hpa --restart=Never --rm /bin/sh <br>while true; do wget -q -O- http://10.244.1.113; done<br>
查看hpa是否工作
watch -d kubectl get hpa<br><br>Every 2.0s: kubectl get hpa Sun Jul 24 15:08:46 2022<br><br>NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE<br>hpa-demo Deployment/hpa-demo 800%/10% 1 10 4 116s<br>
查看Pod是否扩容
kubectl get po -l app=nginx<br><br>kubectl get deployment hpa-demo<br>
注意:从 Kubernetes v1.12 版本开始我们可以通过设置 kube-controller-manager 组件的--horizontal-pod-autoscaler-downscale-stabilization 参数来设置一个持续时间,用于指定在当前操作完成后,HPA 必须等待多长时间才能执行另一次缩放操作。默认为5分钟,也就是默认需要等待5分钟后才会开始自动缩放
5、当我们停止对Pod的请求,cpu利用率下去,那么hpa就会自动缩容pod的个数
HPA演示-基于内存
1、创建资源对象<br>https://github.com/tzh666/k8s-yaml/tree/main/HPA/hpa-mem-demo.yaml<br>https://github.com/tzh666/k8s-yaml/tree/main/HPA/hpa-mem.yaml<br>
2、压测,进入创建的hpa-mem-demo Pod,执行脚本<br>kubectl exec -it hpa-mem-demo-66944b79bf-tqrn9 /bin/bash<br>source /etc/script/increase-mem.sh<br>
3、查看hpa状态,超过60就会扩容<br>watch -d kubectl get hpa<br>
4、停止压测脚本,内存使用率降低,hpa会在5分钟后缩容Pod
HPA扩容缩容失败排查
1、controller-manager 默认5分钟过后会进行缩放,所有需要等待五分钟后查看<br> 可以查看controller-mannager日志
2、也可以直接describe hpa 的事件
3、或者查看metrics-server、kubelet等日志
深入掌握Service
Service是什么?
service是kubernetes实现微服务架构的核心概念,通过创建svc,可以为一组具有相同功能的容器应用提供一个统一的入口地址,<br>并且将请求负载分发到后端的各个容器应用上<br>
通过ip+port的形式访问,svc工作在TCP/IP层(4层)
Service的类型(svc访问方式)
ClusterIP:虚拟服务IP地址,该地址用于k8s内部的Pod访问,在Node上kube-proxy通过设置iptables规则进行转发
NodePort:使用宿主机的端口进,使能够访问各Node的外部客户端通过Noded的IP和端口号就能访问服务(默认情况kube-proxy会将端口会绑定到全部网卡)
LoadBalancer:<b>使用外接负载均衡器完成到服务的负载分发,</b>需要在spec.status.loadBalancer字段指定外部负载均衡器的IP地址(云的lb地址),同时定义nodePort和clusterIP,<b>用于公有云环境<br><br></b>https://github.com/tzh666/k8s-yaml/blob/main/pod-Service/lb-svc.yaml<br>
ExternalName:通过返回 CNAME 和它的值,可以将服务映射到 externalName 字段的内容(例如, foo.bar.example.com, 或者IP)
ExternalName 是 Service 的特例,它没有 selector,也没有定义任何的端口和 Endpoint,也就是没对应的Pod。<br>对于运行在集群外部的服务,它通过返回该外部服务的别名这种方式来提供服务<br><br>https://github.com/tzh666/k8s-yaml/blob/main/pod-Service/Externalname.yaml<br>
Service配置文件注意点
clusterIP:当type=LoadBalancer的时候才需要指定
spec.sessionAffinity
是否支持session,可选值为ClientIP,默认Node<br>ClientIP:表示将同一个客户端(根据客户端的ip地址决定)的访问请求都转发到同一个后端Pod
Service实战
定义 Service<br>https://github.com/tzh666/k8s-yaml/blob/main/pod-Service/myservice.yaml<br>
1、创建一个名为 myservice 的 Service 对象,它会将请求代理到<b>使用 TCP 端口为 8080,具有标签 app=myapp 的 Pod 上</b><br>
2、这个 Service 会被系统分配一个 Cluster IP,该 Service 还会持续的监听 selector 下面的 Pod,<br><b>会把这些 Pod 信息更新到一个名为 myservice 的Endpoints(</b>kubectl get ep<b>) 对象上去</b><br>
Service的负载均衡机制
在 Kubernetes 集群中,每个 Node 会运行一个 kube-proxy 进程, 负责为 Service 实现一种 VIP(虚拟 IP,就是我们上面说的 clusterIP)的代理形式,<br>现在的 Kubernetes 中默认是使用的 iptables 这种模式来代理。其他2种userspace(废弃)、ipvs(推荐)<br>
userspace
1、用户空间模式,由kube-proxy完成代理的实现,效率最低,不推荐使用
iptables
1、kube-proxy通过设置Linux Kernel的iptanles规则,实现从Service在转发到后端的EndPoint列表的负载分发规则,且效率很高
2、但是,如果某个后端EndPoint在转发的时不可用,此次客户端请求就会得到失败的相应,相对于userspace模式更加不可靠<br>3、所以得通过Pod设置readinessprobe(服务可用性检查)来保证只有达到ready状态的EndPoint才会被设置为svc的后端ep
缺点
ipvs模式
1、kube-proxy通过设置Linux Kennel的netlink借口设置IPVS规则,转发效率和支持的吞吐率都是最高的
2、ipvs模式要求Linux Kennel启用IPVS模块,注意如果操作系统未启用IPVS内核模块,kube-proxxy则会自动切换至iptables模式
ipvs多种负载策略<br>kubectl get cm -n kube-system kube-proxy -oyaml | grep mode<br>
rr:轮询
lc:最小连接数
dh:目的地址哈希
sh:源地址哈希
sed:最短期望延时
nq:用不排队
kernelsspace模式
Windows Server上的代理模式
会话保持机制
通过参数 service.spec.sessionAffinity
service.spec.sessionAffinityConfig.clientIP.timeoutSeconds 来设置最大会话停留时间(默认值为 10800 秒,即 3 小时
service多端口设置
......
将外部服务定义为svc-通过ep代理
https://www.cnblogs.com/hsyw/p/14461499.html
获取客户端 IP
externalTrafficPolicy: Local
k8s的服务发现机制
环境变量方式
当我们创建一个Pod 会包含一些环境变量信息,记录代理该Pod的svc的信息,也包括其他已经存在的 Service 的环境变量,这样就可以通过环境变量的方式访问其他的svc服务,但是这样只能等待svc创建后的pod才会包含先前的svc信息-----所以这种方式不推荐使用,推荐使用dns的方式<br>kubectl exec -it nginx-deploy-5d59d67564-dfg2s -- env | grep NGINX_SERVICE_SERVICE_HOST<br>kubectl exec -it nginx-deploy-5d59d67564-dfg2s -- env | grep NGINX_SERVICE_SERVICE_PORT<br>
DNS方式
首先需要部署 CoreDNS<br>
普通的 Service
1、会生成 servicename.namespace.svc.cluster.local 的域名,会解析到 Service 对应的 ClusterIP 上
2、在 Pod 之间的调用可以简写成 servicename.namespace
3、如果处于同一个命名空间下面,甚至可以只写成 servicename 即可访问
Headless Service
1、无头服务,就是把 clusterIP 设置为 None 的,会被解析为指定 Pod 的 IP 列表
2、同样还可以通过 podname.servicename.namespace.svc.cluster.local 访问到具体的某一个 Pod
Pod的DNS域名相关特性
<b>对于普通pod来说,k8s会为其设置一个 pod-ip.namespace.pod.cluster.local的域名,其中Pod ip的. 需要替换成-</b><br>
Pod的DNS域名:
<b>如果是Deployment或者DemonSet启动Pod</b>
<b>k8s会为每个Pod都以其IP地址和控制器起名称起一个DNS域名,<br>格式:pod-ip.deployment-name/daemonset-name.namespace.svc.cluster.local,其中Pod ip的. 需要替换成-</b>
# 解析例子,随意起一个busybox即可验证<br>
<b>kind:Pod类型启动的Pod</b>
[root@master01 pod-Service]# kubectl get po -owide<br>NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES<br>nginx-deploy-5d59d67564-dfg2s 1/1 Running 0 20m 10.244.3.39 node03 <none> <none>
# 解析例子,随意起一个busybox即可验证<br>/ # nslookup 10-244-3-39.default.pod.cluster.local<br>Server: 10.96.0.10<br>Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local<br><br>Name: 10-244-3-39.default.pod.cluster.local<br>Address 1: 10.244.3.39 10-244-3-39.nginx.default.svc.cluster.local<br>
为Pod自定义hostname 和 subdomain
pod.spec.hostname
pod.spec.subddomain
创建Pod以后可以进入容器里面看 /etc/hosts 配置文件查看映射的dns信息
Pod的DNS策略
通过 pod.spec.dnsPolicy 设置
支持的策略
Default:继承Pod所在宿主机的域名解析设置
ClusterFirst(默认):优先使用k8s环境的DNS服务(如CoreDNS提供的域名解析服务),讲无法解析的域名转发到系统上配置的上游DNS服务器
ClusterFirstWithHostNet:适用于以hostNetwork模型运行的Pod
Node: 忽略k8s集群配置的DNS,需要手工通过pod.spec.dnsConfig自定义的DNS配置
dnsConfig
nameservers:将用作于 Pod 的 DNS 服务器的 IP 地址列表。 最多可以指定 3 个 IP 地址。当 Pod 的 dnsPolicy 设置为 "None" 时,列表必须至少包含一个 IP 地址,否则此属性是可选的。所列出的服务器将合并到从指定的 DNS 策略生成的基本名称服务器,并删除重复的地址。<br>
<span style="color: rgba(0, 0, 0, 0.87); font-family: "Noto Sans", sans-serif; font-size: 16px;">searches:用于在 Pod 中查找主机名的 DNS 搜索域的列表。此属性是可选的。 指定此属性时,所提供的列表将合并到根据所选 DNS 策略生成的基本搜索域名中。重复的域名将被删除,Kubernetes 最多允许 6 个搜索域</span>
options:可选的对象列表,其中每个对象可能具有 name 属性(必需)和 value 属性(可选)。此属性中的内容将合并到从指定的 DNS 策略生成的选项。重复的条目将被删除
apiVersion: v1<br>kind: Pod<br>metadata:<br> namespace: default<br> name: dns-example<br>spec:<br> containers:<br> - name: test<br> image: nginx<br> dnsPolicy: "None"<br> dnsConfig:<br> nameservers:<br> - 1.2.3.4<br> searches:<br> - ns1.svc.cluster-domain.example<br> - my.dns.search.suffix<br> options:<br> - name: ndots<br> value: "2"<br> - name: edns0
DNS优化
https://www.qikqiak.com/k8strain2/network/localdns/
在 resolv.conf 中就有两个相关的参数可以进行配置
single-request-reopen:发送 A 类型请求和 AAAA 类型请求使用不同的源端口,这样两个请求在 conntrack 表中不占用同一个表项,从而避免冲突
single-request:避免并发,改为串行发送 A 类型和 AAAA 类型请求。没有了并发,从而也避免了冲突
要给容器的 resolv.conf 加上 options 参数,有几个办法
在容器的 ENTRYPOINT 或者 CMD 脚本中,执行 /bin/echo 'options single-request-reopen' >> /etc/resolv.conf
在 Pod 的 postStart hook 中添加
lifecycle:<br> postStart:<br> exec:<br> command:<br> - /bin/sh<br> - -c <br> - "/bin/echo 'options single-request-reopen' >> /etc/resolv.conf"
使用 template.spec.dnsConfig 配置
template:<br> spec:<br> dnsConfig:<br> options:<br> - name: single-request-reopen
使用 ConfigMap 覆盖 Pod 里面的 /etc/resolv.conf
# configmap<br>apiVersion: v1<br>data:<br> resolv.conf: |<br> nameserver 1.2.3.4<br> search default.svc.cluster.local svc.cluster.local cluster.local<br> options ndots:5 single-request-reopen timeout:1<br>kind: ConfigMap<br>metadata:<br> name: resolvconf<br>---<br># Pod Spec<br>spec:<br> volumeMounts:<br> - name: resolv-conf<br> mountPath: /etc/resolv.conf <br> subPath: resolv.conf # 在某个目录下面挂载一个文件(保证不覆盖当前目录)需要使用subPath -> 不支持热更新<br>...<br> volumes:<br> - name: resolv-conf<br> configMap:<br> name: resolvconf<br> items:<br> - key: resolv.conf<br> path: resolv.conf
或者是给DNS增加缓存机制(NodeLocal DNSCache)<br><b><br>推荐使用</b>
作用
1、NodeLocal DNSCache 通过在集群节点上运行一个 DaemonSet 来提高集群 DNS 性能和可靠性
2、处于 ClusterFirst 的 DNS 模式下的 Pod 可以连接到 kube-dns 的 serviceIP 进行 DNS 查询,通过 kube-proxy 组件添加的 iptables 规则将其转换为 CoreDNS 端点
3、通过在每个集群节点上运行 DNS 缓存,NodeLocal DNSCache 可以缩短 DNS 查找的延迟时间、使 DNS 查找时间更加一致,以及减少发送到 kube-dns 的 DNS 查询次数
在集群中运行 NodeLocal DNSCache的优点<br>
如果本地没有 CoreDNS 实例,则具有最高 DNS QPS 的 Pod 可能必须到另一个节点进行解析,使用 NodeLocal DNSCache 后,拥有本地缓存将有助于改善延迟
跳过 iptables DNAT 和连接跟踪将有助于减少 conntrack 竞争并避免 UDP DNS 条目填满 conntrack 表(上面提到的5s超时问题就是这个原因造成的)
从本地缓存代理到 kube-dns 服务的连接可以升级到 TCP,TCP conntrack 条目将在连接关闭时被删除,而 UDP 条目必须超时(默认 nfconntrackudp_timeout 是 30 秒)
将 DNS 查询从 UDP 升级到 TCP 将减少归因于丢弃的 UDP 数据包和 DNS 超时的尾部等待时间,通常长达 30 秒(3 次重试+ 10 秒超时)
缺点
缺点就是由于 LocalDNS 使用的是 DaemonSet 模式部署,所以如果需要更新镜像则可能会中断服务(不过可以使用一些第三方的增强组件来实现原地升级解决这个问题,比如 openkruise)
部署NodeLocal DNSCache
# 需要注意的是这里使用 DaemonSet 部署 node-local-dns 使用了 hostNetwork=true,会占用宿主机的 8080 端口,所以需要保证该端口未被占用<br>wget https://github.com/kubernetes/kubernetes/raw/master/cluster/addons/dns/nodelocaldns/nodelocaldns.yaml<br>
资源清单文件中包含几个变量值得注意,其中:<br><br>__PILLAR__DNS__SERVER__ :表示 kube-dns 这个 Service 的 ClusterIP,可以通过命令 kubectl get svc -n kube-system | grep kube-dns | awk'{ print $3 }' 获取(我们这里就是 10.96.0.10)<br>__PILLAR__LOCAL__DNS__:表示 DNSCache 本地的 IP,默认为 169.254.20.10<br>__PILLAR__DNS__DOMAIN__:表示集群域,默认就是 cluster.local
还有两个参数 __PILLAR__CLUSTER__DNS__ 和 __PILLAR__UPSTREAM__SERVERS__,这两个参数会通过镜像 1.15.16 版本去进行自动配置,对应的值来源于 kube-dns 的 ConfigMap 和定制的 Upstream Server 配置
$ sed 's/k8s.gcr.io\/dns/cnych/g<br>s/__PILLAR__DNS__SERVER__/10.96.0.10/g<br>s/__PILLAR__LOCAL__DNS__/169.254.20.10/g<br>s/__PILLAR__DNS__DOMAIN__/cluster.local/g' nodelocaldns.yaml |<br>kubectl apply -f -
# 查看是否启动成功<br>kubectl get pods -n kube-system -l k8s-app=node-local-dns<br>
注意 kube-proxy的转发模式:
ipvs 模式
1、如果 kube-proxy 组件使用的是 ipvs 模式的话我们还需要修改 kubelet 的 --cluster-dns 参数,将其指向 169.254.20.10,Daemonset 会在每个节点创建一个网卡来绑这个 IP
2、Pod 向本节点这个 IP 发 DNS 请求,缓存没有命中的时候才会再代理到上游集群 DNS 进行查询
iptables 模式
iptables 模式下 Pod 还是向原来的集群 DNS 请求,节点上有这个 IP 监听,会被本机拦截,再请求集群上游 DNS,所以不需要更改 --cluster-dns 参数
如果担心线上环境修改 --cluster-dns 参数会产生影响,我们也可以直接在新部署的 Pod 中通过 dnsConfig 配置使用新的 localdns 的地址来进行解析
Ingress
ingress一些介绍
Ingress工作在7层网络协议,可以通过不同的URL地址对应到不同的后端或者虚拟服务器
Ingress只能以http、https协议提供服务
<font color="#d32f2f">使用ingres进行服务路由时</font>,<b>ingress Controller基于Ingress规则将客户端请求直接转发到Service对应的后端EndPoint(pod)上</b>,<br><b>这样就会跳过kube-proxy设置的路由转发规则,提高网络转发率</b><br>
Ingress 其实就是从 Kuberenets 集群外部访问集群的一个入口,将外部的请求转发到集群内不同的 Service 上,其实就相当于 nginx、haproxy 等负载均衡代理服务器
Ingress Controlle分类
当然你也可以自己实现一个 Ingress Controller<br><b>现在普遍用得较多的是 traefik 和 nginx-controller,traefik 的性能较 nginx-controller 差,但是配置使用要简单许多</b><br>
traefik
nginx-controller
Kubernetes Ingress Controller for Kong
HAProxy Ingress controller
NGINX Ingress Controller 安装<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/NGINX-Ingress-Controller%E5%AE%89%E8%A3%85.md<br>
部署要注意的:<br><ul><li>nginx-ingress 所在的节点需要能够访问外网,域名才可以解析到这些节点上直接使用</li><li>需要让 nginx-ingress 绑定节点的 80 和 443 端口,所以可以使用 hostPort 来进行访问</li><li>测试此处采用 hostNetwork 模式(生产环境可以使用 LB + DaemonSet hostNetwork 模式)</li><li>当然对于线上环境来说为了保证高可用,一般是需要运行多个 nginx-ingress 实例</li><li>然后可以用一个 nginx/haproxy 作为入口,通过 keepalived 来访问边缘节点的 vip 地址</li></ul>
边缘节点:<br><ul><li>所谓的边缘节点即集群内部用来向集群外暴露服务能力的节点</li><li>集群外部的服务通过该节点来调用集群内部的服务</li><li>边缘节点是集群内外交流的一个Endpoint</li></ul>
# ingress 访问流程图<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ingress-controller-workflow.png<br>
<ul><li>户端首先对 域名(xxx.yyy.com) 执行 DNS 解析,得到 Ingress Controller 所在节点的 IP</li><li>然后客户端向 Ingress Controller 发送 HTTP 请求,然后根据 Ingress 对象里面的描述匹配域名,找到对应的 Service 对象,并获取关联的 Endpoints 列表,将客户端的请求转发给其中一个 Pod</li></ul>
Ingress 实战
# 为一个 nginx 应用创建一个 Ingress 资源<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ngdemo.yaml<br><br><b>资源创建成功后,可以将域名 yyy.xxx.com 解析到 ingress-nginx 所在的边缘节点中的任意一个IP (当然也可以用一个 nginx/haproxy 作为入口,通过 keepalived 来访问边缘节点的 vip 地址)</b><br>
<b># 当然也可以在本地/etc/hosts中添加对应的映射也可以,然后就可以通过域名进行访问<br><br>echo "ingress controller IP 域名" >> /etc/hosts<br> echo "192.168.1.74 tzh.zhenhuan.com" >> /etc/hosts<br><br>然后就可以curl tzh.zhenhuan.com进行测试了</b>
# rewrite的组要功能是实现RUL地址的重定向<br># nginx 参考:https://www.cnblogs.com/zhijiyiyu/p/15173592.html<br> URL Rewrite()<br>
# 部署一个正常访问的demo<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/mongo.yaml<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/web.yaml<br>
<b># 测试环境<br></b># 部署完成以后,再次映射hosts文件是可以通过域名去访问的(查看web.yaml)文件可以知道域名是todo.tzh.com<br>
# 需求是在访问域名中匹配前缀 app,然后通过 rewrite-target 指定目标 --> http://todo.tzh.com/app/<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ingress-rewrite.yaml<br>
apiVersion: extensions/v1beta1<br>kind: Ingress<br>metadata:<br> name: todo<br> annotations:<br> kubernetes.io/ingress.class: "nginx"<br> nginx.ingress.kubernetes.io/app-root: /app/ # 注解 app-root: 访问主域名的时候会自动跳转到我们指定的 app-root 目录下面,相当于在域名后面拼接这个字符串/app/<br> nginx.ingress.kubernetes.io/rewrite-target: /$2 # rewrite-target 类似于nginx的rewrite<br> nginx.ingress.kubernetes.io/configuration-snippet: | # configuration-snippet 对静态资源做一次跳转<br> rewrite ^(/app)$ $1/ redirect; # 在url最末尾加 "/", 使访问http://todo.tzh.com/app的时候拼接成http://todo.tzh.com/app/,添加后我们的应用就都会以 / 这样的 slash 结尾了<br> rewrite ^/stylesheets/(.*)$ /app/stylesheets/$1 redirect; # 添加 /app 前缀<br> rewrite ^/images/(.*)$ /app/images/$1 redirect; # 添加 /app 前缀<br>spec:<br> rules: # 一个Ingress可以配置多个rules<br> - host: todo.tzh.com # 所代理的域名, 域名配置,可以不写,匹配*,或者写一半 *.bar.com<br> http:<br> paths:<br> - backend:<br> serviceName: todo # 所代理svc的名字<br> servicePort: 3000 # svc使用的cluster端口<br> path: /app(/|$)(.*) # 相当于nginx的location配合,同一个host可以配置多个path # 这个相当于loaclhost 那个路径,上面的就是配置跳转<br>
# Basic Auth 认证
用 htpasswd 生成一个密码文件来验证身份验证<br>yum -y install httpd-tools<br>htpasswd -c auth tzh<br><br>[root@master01 Ingress]# cat auth <br>tzh:$apr1$OlbPHgLK$S.S1ixNAlQ9SuZvBJgrup.<br><br>[root@master01 Ingress]# kubectl create secret generic basic-auth --from-file=auth<br>secret/basic-auth created<br>
# 然后创建个ingress<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/web-https.yaml<br>
灰度发布
https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ingress-%E7%81%B0%E5%BA%A6%E5%8F%91%E5%B8%83.pdf
ingress 手动设置HTTPS
1、手动签发证书(自签证书浏览器不忍,生产可以自己买)<br>openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=todo.tzh.com"<br>
2、Secret 对象来引用证书文件(# 要注意证书文件名称必须是 tls.crt 和 tls.key)<br>kubectl create secret tls foo-tls --cert=tls.crt --key=tls.key<br>
3、创建一个 HTTPS 访问应用的ingress<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/web-https.yaml<br>
4、访问<br>https://todo.tzh.com/<br>
CertManager 自动 HTTPS
CertManager是什么?<br>1、官网https://cert-manager.io/<br>2、是一个云原生证书管理开源项目,用于在 Kubernetes 集群中提供 HTTPS 证书并自动续期,支持 Let's Encrypt/HashiCorp/Vault 这些免费证书的签发<br>3、在 Kubernetes 中,可以通过 Kubernetes Ingress 和 Let's Encrypt 实现外部服务的自动化 HTTPS
# 架构图<br>上面是官方给出的架构图,可以看到 cert-manager 在 Kubernetes 中定义了两个自定义类型资源:Issuer(ClusterIssuer) 和 Certificate<br>
Issuer 代表的是证书颁发者,可以定义各种提供者的证书颁发者,当前支持基于 Let's Encrypt/HashiCorp/Vault 和 CA 的证书颁发者,还可以定义不同环境下的证书颁发者
Certificate 代表的是生成证书的请求,一般其中存入生成证书的元信息,如域名等
1、一旦在 Kubernetes 中定义了上述两类资源,部署的 cert-manager 则会根据 Issuer 和 Certificate 生成 TLS 证书,并将证书保存进 Kubernetes 的 Secret 资源中,然后在 Ingress 资源中就可以引用到这些生成的 Secret 资源作为 TLS 证书使用,<br>2、对于已经生成的证书,还会定期检查证书的有效期,如即将超过有效期,还会自动续期<br>
# 部署 CertManager<br># Kubernetes 1.16+<br>➜ kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager.yaml<br><br># Kubernetes <1.16<br>➜ kubectl apply --validate=false -f https://github.com/jetstack/cert-manager/releases/download/v1.1.0/cert-manager-legacy.yaml<br>
# 部署成功<br>[root@master01 Ingress]# kubectl get pods -n cert-manager<br>NAME READY STATUS RESTARTS AGE<br>cert-manager-5597cff495-686lr 1/1 Running 0 7m55s<br>cert-manager-cainjector-bd5f9c764-9wxmd 1/1 Running 0 7m55s<br>cert-manager-webhook-5f57f59fbc-bbz7k 1/1 Running 0 7m55s<br>
# 验证下是否可以签发基本的证书类型,创建一个 Issuer 资源对象来测试 webhook 工作是否正常(在开始签发证书之前,必须在群集中至少配置一个 Issuer 或 ClusterIssuer 资源)<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/test-selfsigned.yaml<br><br>这里我们创建了一个名为 cert-manager-test 的命名空间,创建了一个自签名的 Issuer 证书颁发机构,然后使用这个 Issuer 来创建一个证书请求的 Certificate 对象<br><br>kubectl describe certificate -n cert-manager-test<br>kubectl get secret -n cert-manager-test selfsigned-cert-tls -o yaml<br><br><b>到这里证明我们的 cert-manager 已经安装成功了</b><br>
自动化 HTTPS
HTTP-01 校验<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ingress-auto-https.pdf<br>
DNS-01 校验<br>https://github.com/tzh666/k8s-yaml/blob/main/Ingress/ingress-auto-https.pdf<br>
核心组件的运行机制
kube-apiserver
1、核心功能是提供k8s各类资源对象(如Pod、RC、SVC等)的CRUD及Watch等HTTPS接口,成为<b>集群内部各个功能模块之间数据交互的中心枢纽,是整个系统的数据总线和数据中心。<br></b>2、k8s核心组件唯一对ETCD数据库直接进行数据交互的组件<br>3、是集群管理的API入口,是资源配额配置的入口,提供了完备的集群安全机制。
1、kube-apiserver进程运行在Master上,默认端口8080<br>2、API Server 架构图<br>https://www.processon.com/view/link/62e4dfff1e0853070698fd2f<br>
架构图说明
API层
主要以REST方式提供各种API接口,除了k8s资源对象的CRUD和Watch等主要API,还有健康检查、UI、日志、性能指标等运维监控相关API
访问控制层
当客户端访问API接口时,访问控制层负责对用户身份鉴权、验明用户身份,核准用户对k8s资源对象的访问权限,然后根据配置各种资源访问许可逻辑,判断是否允许访问
注册表层
k8s把所有资源对象都保存在注册表(Register)中,针对注册表中的各种资源对象都定义了资源对象的类型,如何创建资源对象、如何转换资源的不同版本,以及如何将资源编码和解码为JSON或者ProtoBuf进行存储
etcd数据库
1、用于持久化存储k8s资源对象的KV数据库<br>2、etcd的watch接口对于API Server来说至关重要,因为通过这个接口,API Server可以管理超大规模的集群,以及相应和快速处理集群中的各种事件
主要储存:节点健康状况、节点资源、节点名称、节点信息、操作系统版本、Docker版本、kubelet版本等
Controller Manager
# 作用<br>1、在kubernetes集群中,每一个Controller都是一个智能系统、自动系统,它们通过API Server提供的(List-Watch)接口实时监控集群中特定资源的状态变化,<b>当发生各种故障导致某种资源对象的状态变化时,Controller会尝试将其状态调整为期望的状态。<br></b>2、<b>Controller Manager是k8s中各种操作系统的管理者,是集群内部的控制管理中心,也是自动化功能的核心</b><br>
Controller Manager内部包含诸多控制器
Replication Controller
副本控制器<br><b>注意:这不是资源对象的RC(Replication Controller),而是副本控制器</b><br>
核心作用:(基本上不用,用deploy controller)<br>1、确保资源对象RC关联的Pod副本数量在任何时候都保持预设值。多了就-,少了就+,=才是预设值<br>注意:<br>1、只有当Pod的重启策略是Always时,Replicate Controller才会管理该Pod的操作(如创建、销毁、重启等)
Deployment Controller
也是副本控制器,取代了Replication Controller
作用-应用场景:<br>1、确保在当前集群中有且仅有N个Pod实例,N是在资源对象RC中定义的副本数<br>2、调整spec.replicas 属性的值来实现系统的扩缩容<br>3、通过改变Pod模板(主要是image 版本)来实现系统的滚动升级
Node Controller
通过API Server实时获取Node的相关信息,事件管理和监控集群中各个Node的相关控制功能
ResourceQuota Controller
资源配额管理是通过Adminssion Control(准入控制)来控制的<br>官网:https://kubernetes.io/zh-cn/docs/concepts/policy/resource-quotas/<br><br>作用:<br>1、能确保指定对象在任何时候都不会超量占用系统物理资源,避免由于某些业务进程在设计或实现上的存在的缺陷导致整个系统允许絮乱甚至意外宕机,对整个集群平稳运行和稳定性都有非常重要的作用<br>
三个层次的资源配额管理
容器级别,可以对CPU和Memory进行限制
Pod级别,可以对一个Pod内的所以容器的可用资源进行限制
Namespace级别,为ns(多租用户),级别的资源限制,包括:Pod数量、资源对象RC数量、SVC数量、ResourcesQuota数量、Secret数量和可持有的PV数量
资源配额管理是通过Adminssion Control(准入控制)来控制的,有2种方式提供配额约束<br>
LimitRanger
作用于Pod、Container
ResourceQuota
作用于Namspace,限定于ns里的各种资源的使用总额
Namespeace Controller
ServiceAccount Controller
安全相关控制器
Token Controller
安全相关控制器
Service Controller
EndPoint Controller
Router Controller
<b>公有云厂商提供的进行节点扩缩容时管理节点路由的控制器<br></b>例如:谷歌云GCE平台里动态添加一个虚拟机作为节点时,相应的路由策略、防火墙规则等配置无效用户手动添加,都能依靠这个控制器自动完成
Volume Controller
等等等等等等
Schedule
作用:<br>1、Schedule在整个系统种承担了 "承上启下"的重要功能,“承上”是指它负责接收Controller Manager创建的新Pod,为其安排一个落脚的家---调度到Node;"启下"是指安置工作完成以后,目标Node上的kubelet服务进程接管后续工作,负责Pod生命周期的"下半生"
Schedule只跟API Server打交道
输入:待调度的Pod和全部计算节点的信息
输出:目标Pod要"安家"的最优节点
调度过程:
预选节点
1、遍历所有节点,筛选出符合要求的候选节点。在此阶段会将所有不合适的节点全部过滤,只留下合适的节点
2、如果一个节点都没有,那么Pod的状态Pending
打分阶段(优选节点)
1、从预选节点中,采用优选策略计算出每个候选节点的积分,积分最高的就把Pod调度过去
kubelet
什么是kulebet
1、在每个Node节点上都会启动一个kubelet进程服务,(当然你Master想起服务也可以启动个kubelet)
2、kubelet用于处理master下发到本节点的任务,管理Pod以及Pod中的容器
3、每个kubelet都会像API Server上注册节点的信息,定期像Master汇报节点的资源使用情况,并通过cAdvisor监控容器和节点资源(默认10s), --node-status-update-frequency 可以设置
kubelet作用
节点管理
1、节点通过kubelet的启动参数 "--register-node",来决定是否像API Server注册自己
2、true时注册,<br>--api-server:API Server的位置<br>--kubeconfig文件,用于访问API Server的安全配置<br>--cloud-provider:云服务商地址,仅用于公有云中
Pod管理
1、静态Pod配置文件目录
2、HTTP配置Pod文件路径<br>
3、API Server:kubelet通过API Server监听Etcd目录,同步Pod列表
容器监控检查
参考容器的监控检查即可
cAdisor资源监控
1、1.12关闭
2、1.13可以部署个ds提供收集指标
3、新的都用metrics
容器运行时
kube-porxy
<b>核心功能:将某个SVC的访问请求,转发到后端的多个Pod实例上</b>
第一代
user space弃用
第二代
iptables,不推荐使用
第三代
IPVS,推荐使用
优点
1、为大型集群提供了更好的可扩展性和性能
2、支持比iptables更复杂的负载均衡算法
3、支持服务器健康检查、连接重试等功能
4、可动态修改ipset的集合,即使iptables的规则正在使用这个集合
ipset:理解成一个IP段的集合,就是这玩意很吊吧。<br>这个是带索引的数据结构,这个集合可以是IP段、ip、port等。这样就减少了使用iptables规则,设置少量的规则即可实现目的
0 条评论
下一页