Api server
此 master 组件提供 Kubernetes API。这是Kubernetes控制平台的前端(front-end),可以水平扩展(通过部署更多的实例以达到性能要求)。kubectl / kubernetes dashboard / kuboard 等Kubernetes管理工具就是通过 kubernetes API 实现对 Kubernetes 集群的管理。
etcd
支持一致性和高可用的名值对存储组件,Kubernetes集群的所有配置信息都存储在 etcd 中。
Scheduer
此 master 组件监控所有新创建尚未分配到节点上的 Pod,并且自动选择为 Pod 选择一个合适的节点去运行。
影响调度的因素有:
- 单个或多个 Pod 的资源需求
- 硬件、软件、策略的限制
- 亲和与反亲和(affinity and anti-affinity)的约定
- 数据本地化要求
- 工作负载间的相互作用
kube-controller-manager
此 master 组件运行了所有的控制器
逻辑上来说,每一个控制器是一个独立的进程,但是为了降低复杂度,这些控制器都被合并运行在一个进程里。
kube-controller-manager 中包含的控制器有:
- 节点控制器: 负责监听节点停机的事件并作出对应响应
- 副本控制器: 负责为集群中每一个 副本控制器对象(Replication Controller Object)维护期望的 Pod 副本数
- 端点(Endpoints)控制器:负责为端点对象(Endpoints Object,连接 Service 和 Pod)赋值
- Service Account & Token控制器: 负责为新的名称空间创建 default Service Account 以及 API Access Token。
cloud-controller-manager
cloud-controller-manager 中运行了与具体云基础设施供应商互动的控制器。这是 Kubernetes 1.6 版本中引入的特性,尚处在 alpha 阶段。
cloud-controller-manager 只运行特定于云基础设施供应商的控制器。
cloud-controller-manager 使得云供应商的代码和 Kubernetes 的代码可以各自独立的演化。在此之前的版本中,Kubernetes的核心代码是依赖于云供应商的代码的。在后续的版本中,特定于云供应商的代码将由云供应商自行维护,并在运行Kubernetes时链接到 cloud-controller-manager。
以下控制器中包含与云供应商相关的依赖:
-
节点控制器:当某一个节点停止响应时,调用云供应商的接口,以检查该节点的虚拟机是否已经被云供应商删除。
-
路由控制器:在云供应商的基础设施中设定网络路由。
-
服务(Service)控制器:创建、更新、删除云供应商提供的负载均衡器。
-
数据卷(Volume)控制器:创建、绑定、挂载数据卷,并协调云供应商编排数据卷。
kubelet
此组件是运行在每一个集群节点上的代理程序。它确保 Pod 中的容器处于运行状态。Kubelet 通过多种途径获得 PodSpec 定义,并确保 PodSpec 定义中所描述的容器处于运行和健康的状态。Kubelet不管理不是通过 Kubernetes 创建的容器。
Kube-proxy
kube-proxy是一个网络代理程序,运行在集群中的每一个节点上,是实现 Kubernetes Service 概念的重要部分。
kube-proxy 在节点上维护网络规则。这些网络规则使得您可以在集群内、集群外正确地与 Pod 进行网络通信。如果操作系统中存在 packet filtering layer,kube-proxy 将使用这一特性(iptables代理模式),否则,kube-proxy将自行转发网络请求User space代理模式)
fluentd
日志收集服务。
pod
pod是k8s的最小服务单元,pod内部才是容器,k8s通过操作pod来操作容器。一个Node节点可以有多个Pod
Pod可以说是Node节点中最核心的部分,Pod也是一个容器,它是一个"用来封装容器的容器"。一个Pod中往往会装载多个容器,这些容器共用一个虚拟环境,共享着网络和存储等资源。 这些容器的资源共享以及相互交互都是由pod里面的pause容器来完成的,每初始化一个pod时便会生成一个pause容器。
一个POD可以包含多个容器,这些容器共享着POD上的资源(如网络IP,存储空间等) POD是集群上最基础的单元 每个POD对与其对应的节点Node绑定,一个POD对应着一个IP。
Pod需要在Node上运行,一个Node可以运行着多个Pod,一个Node对应着集群中的一台机器。 Node节点由Master节点统一管理,Master会根据各Node的资源可用程度自动调度Pod到不同的Node上。
每个 Kubernetes Node(节点)至少运行:
- Kubelet,负责 master 节点和 worker 节点之间通信的进程;管理 Pod(容器组)和 Pod(容器组)内运行的 Container(容器)。
- 容器运行环境(如Docker)负责下载镜像、创建和运行容器等。
Deployment 译名为 部署。在k8s中,通过发布 Deployment,可以创建应用程序 (docker image) 的实例 (docker container),这个实例会被包含在称为 Pod 的概念中,Pod 是 k8s 中最小可管理单元。
在 k8s 集群中发布 Deployment 后,Deployment 将指示 k8s 如何创建和更新应用程序的实例,master 节点将应用程序实例调度到集群中的具体的节点上。
创建应用程序实例后,Kubernetes Deployment Controller 会持续监控这些实例。如果运行实例的 worker 节点关机或被删除,则 Kubernetes Deployment Controller 将在群集中资源最优的另一个 worker 节点上重新创建一个新的实例。这提供了一种自我修复机制来解决机器故障或维护问题。
在容器编排之前的时代,各种安装脚本通常用于启动应用程序,但是不能够使应用程序从机器故障中恢复。通过创建应用程序实例并确保它们在集群节点中的运行实例个数,Kubernetes Deployment 提供了一种完全不同的方式来管理应用程序。
apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment #该配置的类型,我们使用的是 Deployment
metadata: #译名为元数据,即 Deployment 的一些基本属性和信息
name: nginx-deployment #Deployment 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
replicas: 1 #使用该Deployment创建一个应用程序实例
selector: #标签选择器,与上面的标签共同作用,目前不需要理解
matchLabels: #选择包含标签app:nginx的资源
app: nginx
template: #这是选择或创建的Pod的模板
metadata: #Pod的元数据
labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
app: nginx
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
- name: nginx #container的名称
image: nginx:1.8 #使用镜像nginx:1.8创建container,该container默认80端口可访问
#serviceAccountName: feng
应用yaml文件:
kubectl apply -f nginx-deployment.yaml
deployment.apps/nginx-deployment created
查看部署结果:
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 1/1 1 1 56s
kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-deployment-67c77fc7c8-qhnnz 1/1 Running 0 106s
应用程序所在的Pod是一直变动着的,而每个Pod的ip又不一样,但是对于前端用户来说,应用程序的访问地址应该是唯一的才行。 因此k8s提供了一个机制用来为前端屏蔽后端Pod变动带来的IP变动,这便是Service。 Service为一系列有相同特征的Pod(一个应用的Pod在不停变换,但是不论怎么变换这些Pod都有相同的特征)定义了一个统一的访问方式, Service是通过标签选择器(LabelSelector)来识别有哪些Pod有相同特征(带有特定Lable标签的POD,Lable可以由用户设置,标签存在于所有K8S对象上并不仅仅局限于Pod) 可以编成一个容器组的。 Service有三种选项暴露应用程序的入口,可以通过设置应用程序配置文件中的Service 项的spec.type 值来调整。
- ClusterIP(默认) 在集群中的内部IP上公布服务,这种方式的 Service(服务)只在集群内部可以访问到
- NodePort
使用 NAT 在集群中每个的同一端口上公布服务。这种方式下,可以通过访问集群中任意节点+端口号的方式访问服务
<NodeIP>:<NodePort>
。此时 ClusterIP 的访问方式仍然可用。(当 Kubernetes 创建NodePort
服务时,它会从 Kubernetes 集群配置中定义的标志中指定的范围分配端口。默认情况下,这些端口范围为 30000-32767。) - LoadBalancer 在云环境中(需要云供应商可以支持)创建一个集群外部的负载均衡器,并为使用该负载均衡器的 IP 地址作为服务的访问地址。此时 ClusterIP 和 NodePort 的访问方式仍然可用。
apiVersion: v1
kind: Service
metadata:
name: nginx-service #Service 的名称
labels: #Service 自己的标签
app: nginx #为该 Service 设置 key 为 app,value 为 nginx 的标签
spec: #这是关于该 Service 的定义,描述了 Service 如何选择 Pod,如何被访问
selector: #标签选择器
app: nginx #选择包含标签 app:nginx 的 Pod
ports:
- name: nginx-port #端口的名字
protocol: TCP #协议类型 TCP/UDP
port: 80 #集群内的其他容器组可通过 80 端口访问 Service
nodePort: 32600 #通过任意节点的 32600 端口访问 Service
targetPort: 80 #将请求转发到匹配 Pod 的 80 端口
type: NodePort #Serive的类型,ClusterIP/NodePort/LoadBalancer
kubectl apply -f nginx-service.yaml
service/nginx-service created
kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 14h
nginx-service NodePort 10.96.234.122 <none> 80:32600/TCP 3s
这时候访问<任意节点的 IP>:32600就可以访问到pod的nginx服务。不过必须进入到node容器里面:
docker exec -it 8001d159821d /bin/bash
root@k8s-worker:/# curl http://172.19.0.2:32600/
由于 kind 是把 node 运行在 docker 里的, 因此即使在 k8s 集群中使用 nodePort 方式将服务暴露出来,在宿主机上依旧无法访问。
解决办法是Kind 在启动集群时可以指定将某些端口映射出来。
可以通过更改deployment配置文件中的replicas项来设置开启的POD数量 当流量增多导致应用程序POD负载加重后可以通过修改replicas增加POD数量来减轻负担,访问流量将会通过负载均衡在多个POD之间转发
例如将replicas改为4:
apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment #该配置的类型,我们使用的是 Deployment
metadata: #译名为元数据,即 Deployment 的一些基本属性和信息
name: nginx-deployment #Deployment 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
replicas: 4 #使用该Deployment创建一个应用程序实例
selector: #标签选择器,与上面的标签共同作用,目前不需要理解
matchLabels: #选择包含标签app:nginx的资源
app: nginx
template: #这是选择或创建的Pod的模板
metadata: #Pod的元数据
labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
app: nginx
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
- name: nginx #container的名称
image: nginx:1.8 #使用镜像nginx:1.8创建container,该container默认80端口可访问
#serviceAccountName: feng
kubectl apply -f nginx-deployment.yaml
kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-67c77fc7c8-5gx27 1/1 Running 0 2m54s 10.244.1.3 k8s-worker <none> <none>
nginx-deployment-67c77fc7c8-c54vk 1/1 Running 0 2m54s 10.244.1.4 k8s-worker <none> <none>
nginx-deployment-67c77fc7c8-hxb65 1/1 Running 0 2m54s 10.244.1.5 k8s-worker <none> <none>
nginx-deployment-67c77fc7c8-qhnnz 1/1 Running 0 99m 10.244.1.2 k8s-worker <none> <none>
当我们想对已经部署的程序进行升级更新,但又不想让程序停止,就可以使用滚动更新来实现。滚动更新通过使用新版本的POD逐步替代旧版本POD来实现零停机更新。滚动更新是K8S默认的更新方式。
Kubernetes 更新多副本的 Deployment 的版本时,会逐步的创建新版本的 Pod,逐步的停止旧版本的 Pod,以便使应用一直处于可用状态。这个过程中,Service 能够监视 Pod 的状态,将流量始终转发到可用的 Pod 上。
修改nginx的镜像为latest:
apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment #该配置的类型,我们使用的是 Deployment
metadata: #译名为元数据,即 Deployment 的一些基本属性和信息
name: nginx-deployment #Deployment 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
replicas: 4 #使用该Deployment创建一个应用程序实例
selector: #标签选择器,与上面的标签共同作用,目前不需要理解
matchLabels: #选择包含标签app:nginx的资源
app: nginx
template: #这是选择或创建的Pod的模板
metadata: #Pod的元数据
labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
app: nginx
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
- name: nginx #container的名称
image: nginx:latest #使用镜像nginx:1.8创建container,该container默认80端口可访问
#serviceAccountName: feng
kubectl apply -f nginx-deployment.yaml
再不断执行kubectl get pods -l app=nginx
,即可观看pod被逐个替换的过程。
容器按照持续运行的时间可分为两类:
服务类容器
服务类容器通常持续提供服务,需要一直运行,比如 http server,daemon 等。
工作类容器
工作类容器则是一次性任务,比如批处理程序,完成后容器就退出。
Kubernetes 的 Deployment、ReplicaSet 和 DaemonSet 都用于管理服务类容器;对于工作类容器,我们用 Job。
restartPolicy是指当前的重启策略。对于 Job,只能设置为 Never
或者 OnFailure
。对于其他 controller(比如 Deployment)可以设置为 Always
。
节点的状态包含如下信息:
- Addresses
- Conditions
- Capacity and Allocatable
- Info
#获取节点详细信息
kubectl describe node k8s-worker
依据你集群部署的方式(在哪个云供应商部署,或是在物理机上部署),Addesses 字段可能有所不同。
- HostName: 在节点命令行界面上执行
hostname
命令所获得的值。启动 kubelet 时,可以通过参数--hostname-override
覆盖 - ExternalIP:通常是节点的外部IP(可以从集群外访问的内网IP地址;上面的例子中,此字段为空)
- InternalIP:通常是从节点内部可以访问的 IP 地址
Node Condition | 描述 |
---|---|
OutOfDisk | 如果节点上的空白磁盘空间不够,不能够再添加新的节点时,该字段为 True ,其他情况为 False |
Ready | 如果节点是健康的且已经就绪可以接受新的 Pod。则节点Ready字段为 True 。False 表明了该节点不健康,不能够接受新的 Pod。 |
MemoryPressure | 如果节点内存紧张,则该字段为 True ,否则为False |
PIDPressure | 如果节点上进程过多,则该字段为 True ,否则为 False |
DiskPressure | 如果节点磁盘空间紧张,则该字段为 True ,否则为 False |
NetworkUnvailable | 如果节点的网络配置有问题,则该字段为 True ,否则为 False |
如果 Ready
类型Condition 的 status
持续为 Unkown
或者 False
超过 pod-eviction-timeout
的参数)所指定的时间,节点控制器(node controller)将对该节点上的所有 Pod 执行删除的调度动作。默认的 pod-eviction-timeout
时间是 5 分钟。
当apiserver不能和节点的kubelet通信的时候,删除Pod指令无法下达,意味着虽然对Pod执行了删除的调度指令,这些Pod仍然可能在失联的节点上运行。kubernetes v1.5以前会强制删除这些Pod,但之后的版本不会,需要手工删除。
容量和可分配量(Capacity and Allocatable)描述了节点上的可用资源的情况:
- CPU
- 内存
- 该节点可调度的最大 pod 数量
Capacity 中的字段表示节点上的资源总数,Allocatable 中的字段表示该节点上可分配给普通 Pod 的资源总数。
即system info部分的内容,包含例如下面的信息,由节点的kubelet收集
- Linux 内核版本
- Kubernetes 版本(kubelet 和 kube-proxy 的版本)
- Docker 版本
- 操作系统名称
与 Pod 和 Service 不一样,节点并不是由 Kubernetes 创建的,节点由云供应商(例如,Google Compute Engine、阿里云等)创建,或者节点已经存在于您的物理机/虚拟机的资源池。向 Kubernetes 中创建节点时,仅仅是创建了一个描述该节点的 API 对象。节点 API 对象创建成功后,Kubernetes将检查该节点是否有效。
Kubernetes 将保留无效的节点 API 对象,并不断地检查该节点是否有效。除非您使用 kubectl delete node my-first-k8s-node
命令删除该节点。
节点控制器是一个负责管理节点的 Kubernetes master 组件。
- 节点控制器在注册节点时为节点分配 CIDR 地址块
- 节点控制器通过云供应商接口检查节点列表中每一个节点对象对应的虚拟机是否可用。在云环境中,只要节点状态异常,节点控制器检查其虚拟机在云供应商的状态,如果虚拟机不可用,自动将节点对象从 APIServer 中删除。
- 节点控制器监控节点的健康状况。当节点变得不可触达时(接收不到来自节点的心跳信号),节点控制器将节点API对象的
NodeStatus
Condition 取值从NodeReady
更新为Unknown
(默认40s);然后在等待pod-eviction-timeout
(默认为5分钟) 时间后,将节点上的所有 Pod 从节点驱逐。
如果 kubelet 的启动参数 --register-node
为 true(默认为 true),kubelet 会尝试将自己注册到 API Server。kubelet自行注册时,将使用如下选项:
--kubeconfig
:向 apiserver 进行认证时所用身份信息的路径--cloud-provider
:向云供应商读取节点自身元数据--register-node
:自动向 API Server 注册节点--register-with-taints
:注册节点时,为节点添加污点(逗号分隔,格式为<key>=<value>:<effect>
--node-ip
:节点的 IP 地址--node-labels
:注册节点时,为节点添加标签--node-status-update-frequency
:向 master 节点发送心跳信息的时间间隔
管理员可以修改节点api对象:
- 增加/减少标签
- 标记节点为不可调度(unschedulable)
#设置节点为不可调度
kubectl cordon $NODENAME
#设置节点为可调度
kubectl uncordon$NODENAME
节点在向 APIServer 注册的同时,在节点API对象里汇报了其容量(Capacity)。如果您 手动管理节点,您需要在添加节点时自己设置节点的容量。
Kubernetes 调度器在调度 Pod 到节点上时,也需要确保节点上有足够的资源。
所有从集群访问Master节点的通信都是针对apiserver的。
apiserver监听https且配置了一种或多种 客户端认证方式 authentication (opens new window)。至少需要配置一种形式的 授权方式 authorization (opens new window)
节点访问Apiserver的话,节点必须配置apiserver的公钥根证书。此时,在提供有效的客户端身份认证的情况下,节点可以安全地访问 APIServer。节点证书的位置应该是/etc/kubernetes/pki/ca.crt
Pod访问Apiserver的话,应该为其关联 Service Account,此时,Kubernetes将在创建Pod时自动为其注入公钥根证书(public root certificate)以及一个有效的 bearer token(放在HTTP请求头Authorization字段),这些一般都在/var/run/secrets/kubernetes.io/serviceaccount/
下面。
CA_CERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
#因为当前的service account权限不够,所以也没权限访问apiserver
curl --cacert $CA_CERT -H "Authorization: Bearer $TOKEN" https://kubernetes/
所有名称空间中,都默认配置了名为
kubernetes
Kubernetes Service,该 Service对应一个虚拟 IP(默认为 10.96.0.1),发送到该地址的请求将由 kube-proxy 转发到 apiserver 的 HTTPS 端口上。
- apiserver 访问集群中每个节点上的 kubelet 进程
- 使用 apiserver 的 proxy 功能,从 apiserver 访问集群中的任意节点、Pod、Service
apiserver在如下情况访问kubelet:
- 抓取 Pod 的日志
- 通过
kubectl exec -it
指令(或 kuboard 的终端界面)获得容器的命令行终端 - 提供
kubectl port-forward
功能
连接的访问端点是kubelet的https,默认情况不校验kubelet的证书,因此可能会存在man-in-the-middle攻击。
如果要校验 kubelet 的 HTTPS 证书,可以通过 --kubelet-certificate-authority
参数为 apiserver 提供校验 kubelet 证书的根证书。
同时药激活kubelet authentication/authorization(https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/kubelet-authn-authz/)以保护kubelet API。
连接使用的是 HTTP 连接,没有进行身份认证,也没有进行加密传输,此类连接如果运行在非受信网络或公网上时,是 不安全 的。
已不被推荐。
在 Kubernetes 中,控制器 就是上面所说的 控制循环,它不断监控着集群的状态,并对集群做出对应的变更调整。每一个控制器都不断地尝试着将当前状态调整到 目标状态。
在 Kubernetes 中,每个控制器至少追踪一种类型的资源。这些资源对象中有一个 spec
字段代表了目标状态。Kubernetes中,控制器通过与API Server交互来达到调整状态的目的。
某些特殊的控制器需要对集群外部的东西做调整。例如,您想用一个控制器确保集群中有足够的节点,此时控制器需要调用云供应商的接口以创建新的节点或移除旧的节点。这类控制器将从 API Server 中读取关于目标状态的信息,并直接调用外部接口以实现调整目标。
Kubernetes使用了大量的控制器,每个控制器都用来管理集群状态的某一个方面。使用许多个简单的控制器比使用一个全能的控制器要更加有优势。控制器可能会出故障,而这也是在设计 Kubernetes 时要考虑到的事情。
Kubernetes对象指的是Kubernetes系统的持久化实体。Kubernetes将其数据以Kubernetes对象的形式通过 api server存储在 etcd 中。具体来说,这些数据(Kubernetes对象)描述了:
- 集群中运行了哪些容器化应用程序(以及在哪个节点上运行)
- 集群中对应用程序可用的资源
- 应用程序相关的策略定义,例如,重启策略、升级策略、容错策略
- 其他Kubernetes管理应用程序时所需要的信息
每一个 Kubernetes 对象都包含了两个重要的字段:
spec
必须由您来提供,描述了您对该对象所期望的 目标状态status
只能由 Kubernetes 系统来修改,描述了该对象在 Kubernetes 系统中的 实际状态
当您在 Kubernetes 中创建一个对象时,您必须提供
- 该对象的 spec 字段,通过该字段描述您期望的 目标状态
- 该对象的一些基本信息,例如名字
在创建对象的.yaml
文件中,如下字段是必须填写的:
- apiVersion 用来创建对象时所使用的Kubernetes API版本
- kind 被创建对象的类型
- metadata 用于唯一确定该对象的元数据:包括
name
和namespace
,如果namespace
为空,则默认值为default
- spec 描述您对该对象的期望状态
不同类型的 Kubernetes,其 spec
对象的格式不同(含有不同的内嵌字段)
同一个Kubernetes对象应该只使用一种方式管理,否则可能会出现不可预期的结果。
通过向kubectl
命令提供参数直接操作集群中的kubenetes对象。
kubectl run nginx --image nginx
kubectl create deployment nginx --image nginx
缺点:
- 使用命令,无法进行变更review的管理
- 不提供日志审计
- 没有创建新对象的模板
指定具体的操作(create,replace,apply,delete),可选参数以及至少一个配置文件的名字。
kubectl create -f nginx.yaml
kubectl delete -f nginx.yaml -f redis.yaml
replace
指令将直接使用对象中新的 spec 内容替换原有的 spec 内容,如果原有spec中存在配置文件中没有定义的字段,都将被丢弃。这种方法不能够应用在那些 spec 对象独立于配置文件进行更新的情况。例如 LoadBalancer
类型的 Service,其 spec 中的 externalIPs
字段由集群更新。
当使用声明式的对象配置时,用户操作本地存储的Kubernetes对象配置文件,然而,在将文件传递给 kubectl 命令时,并不指定具体的操作,由 kubectl 自动检查每一个对象的状态并自行决定是创建、更新、还是删除该对象。使用这种方法时,可以直接针对一个或多个文件目录进行操作(对不同的对象可能需要执行不同的操作)。
声明式对象配置使用
patch
API接口,此时会把变化的内容更新进去,而不是使用replace
API接口,该接口替换整个 spec 信息。
kubectl diff -f configs/
kubectl apply -f configs/
#递归处理
kubectl diff -R -f configs/
kubectl apply -R -f configs/
(具体的patch的问题没看懂)
Kubernetes REST API 中,所有的对象都是通过 name
和 UID
唯一性确定。
可以通过 namespace
+ name
唯一性地确定一个 RESTFUL 对象,例如:
/api/v1/namespaces/{namespace}/pods/{name}
UID 是由 Kubernetes 系统生成的,唯一标识某个 Kubernetes 对象的字符串。
Kubernetes集群中,每创建一个对象,都有一个唯一的 UID。用于区分多次创建的同名对象。
#查看名称空间
kubectl get namespaces
Kubernetes 安装成功后,默认有初始化了三个名称空间:
- default 默认名称空间,如果 Kubernetes 对象中不定义
metadata.namespace
字段,该对象将放在此名称空间下 - kube-system Kubernetes系统创建的对象放在此名称空间下
- kube-public 此名称空间自动在安装集群是自动创建,并且所有用户都是可以读取的(即使是那些未登录的用户)。主要是为集群预留的,例如,某些情况下,某些Kubernetes对象应该被所有集群用户看到。
当您创建一个 Service 时,Kubernetes 为其创建一个对应的 DNS 条目。该 DNS 记录的格式为 <service-name>.<namespace-name>.svc.cluster.local
。
curl http://nginx-service/
curl http:///nginx-service.default.svc.cluster.local/
并非所有Kubernetes对象都在名称空间中:
# 在名称空间里的对象
kubectl api-resources --namespaced=true
# 不在名称空间里的对象
kubectl api-resources --namespaced=false
apiVersion: v1
kind: Namespace
metadata:
name: development
labels:
name: development
#创建名称空间,一个是生产一个是开发环境
kubectl create -f development-namespace.yaml
kubectl create -f production-namespace.yaml
#查看kubectl上下文
kubectl config view
#查看当前的上下文
kubectl config current-context
#创建上下文以便在两个namespace中工作,设置上下文的namespace,cluster和name(cluster和name从kubectl config view中获得)
kubectl config set-context dev --namespace=development --cluster=kind-k8s --user=kind-k8s
kubectl config set-context prod --namespace=production --cluster=kind-k8s --user=kind-k8s
#切换到dev上下文
kubectl config use-context dev
kubectl config current-context
#创建deployment
kubectl create deployment dev-nginx --image=nginx:1.8 --replicas=2
标签(Label)是附加在Kubernetes对象上的一组名值对,每个Kubernetes对象可以有多个标签,同一个对象的标签的 Key 必须唯一,例如:
metadata:
labels:
key1: value1
key2: value2
标签的key可以有两个部分:可选的前缀和标签名,通过 /
分隔。如果省略标签前缀,则标签的 key 将被认为是专属于用户的。Kubernetes的系统组件(例如,kube-scheduler、kube-controller-manager、kube-apiserver、kubectl 或其他第三方组件)向用户的Kubernetes对象添加标签时,必须指定一个前缀。kubernetes.io/
和 k8s.io/
这两个前缀是 Kubernetes 核心组件预留的
Kubernetes api server支持两种形式的标签选择器,equality-based 基于等式的
和 set-based 基于集合的
。
基于等式的选择方式:
# 选择了标签名为 `environment` 且 标签值为 `production` 的Kubernetes对象
environment = production
# 选择了标签名为 `tier` 且标签值不等于 `frontend` 的对象,以及不包含标签 `tier` 的对象
tier != frontend
#将选择所有a为b且c不为d的对象,逗号代表且
a=b,c!=d
基于集合的选择方式:
支持in
、notin
、exists
:
# 选择所有的包含 `environment` 标签且值为 `production` 或 `qa` 的对象
environment in (production, qa)
# 选择所有的 `tier` 标签不为 `frontend` 和 `backend`的对象,或不含 `tier` 标签的对象
tier notin (frontend, backend)
# 选择所有包含 `partition` 标签的对象
partition
# 选择所有不包含 `partition` 标签的对象
!partition
基于集合的选择方式可以和基于等式的选择方式可以混合使用
list和watch操作的时候可以指定标签选择器作为查询条件:
kubectl get pods -l environment=production,tier=frontend
kubectl get pods -l 'environment in (production),tier in (frontend)'
某些Kubernetes对象(如service和deployment)中也可以使用标签选择器指定一组Kubernetes对象(如pod):
selector:
app: nginx
此外还有一些对象支持同时支持基于等式的选择方式和基于集合的选择方式:
selector:
matchLabels:
component: redis
matchExpressions:
- {key: tier, operator: In, values: [cache]}
- {key: environment, operator: NotIn, values: [dev]}
matchLabels相当于一个map。
Kubernetes 对象的 metadata
字段可以添加自定义的标签(label)或者注解(annotation):
metadata:
annotations:
key1: value1
key2: value2
例子:
metadata:
annotations:
deployment.kubernetes.io/revision: 7 # 由Deployment控制器添加,用于记录当前发布的修改次数
k8s.eip.work/displayName: busybox # Kuboard添加,Deployment显示在Kuboard界面上的名字
k8s.eip.work/ingress: false # Kuboard添加,根据此参数显示Deployment是否配置了Ingress
k8s.eip.work/service: none # Kuboard添加,根据此参数显示Deployment是否配置了Service
字段选择器(Field Selector)可以用来基于的一个或多个字段的取值来选取一组Kubernetes对象,例如:
metadata.name=my-service
metadata.namespace!=default
status.phase=Pending
所有的对象类型都支持的两个字段是 metadata.name
和 metadata.namespace
。
kubectl get pods --field-selector status.phase=Running
Kubernetes中,默认的镜像抓取策略是 IfNotPresent
,即kubelet在发现本机有惊喜的情况下,不会向仓库抓取镜像。
每次启动Pod强制从仓库抓取镜像可以:
- 设置 container 中的
imagePullPolicy
为Always
- 省略
imagePullPolicy
字段,并使用:latest
tag 的镜像 - 省略
imagePullPolicy
字段和镜像的 tag - 激活 AlwaysPullImages (opens new window)管理控制器
imagePullPolicy 字段和 image tag的可能取值将影响到 kubelet 如何抓取镜像:
imagePullPolicy: IfNotPresent
仅在节点上没有该镜像时,从镜像仓库抓取imagePullPolicy: Always
每次启动 Pod 时,从镜像仓库抓取imagePullPolicy
未填写,镜像 tag 为:latest
或者未填写,则同Always
每次启动 Pod 时,从镜像仓库抓取imagePullPolicy
未填写,镜像 tag 已填写但不是:latest
,则同IfNotPresent
仅在节点上没有该镜像时,从镜像仓库抓取imagePullPolicy: Never
,Kubernetes 假设本地存在该镜像,并且不会尝试从镜像仓库抓取镜像
因此在生产环境部署时,您应该避免使用 :latest
tag,如果这样做,您将无法追踪当前正在使用的镜像版本,也无法正确地执行回滚动作(因为每次启动Pod都会从镜像仓库抓取)
在容器中执行hostname,得到的是容器所在Pod的名字:
root@nginx-deployment-56fcf95486-2dxvs:/# hostname
nginx-deployment-56fcf95486-2dxvs
此外容器的环境变量中包含了集群的很多信息,例如集群所有的Service的连接信息都会以环境变量的形式注入到容器中:
root@nginx-deployment-56fcf95486-2dxvs:/# env|grep "SERVICE"
KUBERNETES_SERVICE_PORT_HTTPS=443
SYSTEM_MONITOR_SERVICE_SERVICE_PORT=8080
BUILD_CODE_SERVICE_PORT_3000_TCP_PROTO=tcp
KUBERNETES_GOAT_HOME_SERVICE_SERVICE_PORT=80
KUBERNETES_SERVICE_PORT=443
NGINX_SERVICE_PORT_80_TCP_ADDR=10.96.230.154
KUBERNETES_GOAT_HOME_SERVICE_PORT_80_TCP_PORT=80
BUILD_CODE_SERVICE_PORT=tcp://10.96.76.136:3000
SYSTEM_MONITOR_SERVICE_PORT_8080_TCP_ADDR=10.96.4.100
........
TODO
Kubernetes 中,也为容器提供了对应的生命周期钩子函数,使得容器可以获知其所在运行环境对其进行管理的生命周期事件,以便容器可以响应该事件,并执行对应的代码。
Kubernetes中为容器提供了两个 hook(钩子函数):
-
PostStart
此钩子函数在容器创建后将立刻执行。但是,并不能保证该钩子函数在容器的
ENTRYPOINT
之前执行。该钩子函数没有输入参数。然而Kubernetes 在管理容器时,将一直等到 postStart 事件处理程序结束之后,才会将容器的状态标记为 Running。 -
PreStop
此钩子函数在容器被 terminate(终止)之前执行,例如:
- 通过接口调用删除容器所在 Pod
- 某些管理事件的发生:健康检查失败、资源紧缺等
Kubernetes 只在 Pod
Teminated
状态时才发送 preStop 事件,这意味着,如果 Pod 已经进入了Completed
状态, preStop 事件处理程序将不会被调用。这个问题已经记录在 kubernetes 的 issue 中
Kubernetes 中,容器可以实现两种类型的 hook handler:
- Exec - 在容器的名称空进和 cgroups 中执行一个指定的命令,例如
pre-stop.sh
。该命令所消耗的 CPU、内存等资源,将计入容器可以使用的资源限制。 - HTTP - 向容器的指定端口发送一个 HTTP 请求
定义postStart和preStop处理程序:
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/bin/sh","-c","nginx -s quit; while killall -0 nginx; do sleep 1; done"]
kubectl create -f lifecycle.yaml
kubectl exec -it lifecycle-demo -- /bin/bash
cat /usr/share/message
Hello from the postStart handler
Pod(容器组)是 Kubernetes 中最小的可部署单元。一个 Pod(容器组)包含了一个应用程序容器(某些情况下是多个容器)、存储资源、一个唯一的网络 IP 地址、以及一些确定容器该如何运行的选项。
Pod 中的容器可以:
- 共享资源、依赖
- 互相通信
- 相互协商何时以何种方式结束运行
Pod内的容器有两种类型的共享资源:网络和存储。
网络 Networking:
每一个 Pod 被分配一个独立的 IP 地址。Pod 中的所有容器共享一个网络名称空间:
- 同一个 Pod 中的所有容器 IP 地址都相同
- 同一个 Pod 中的不同容器不能使用相同的端口,否则会导致端口冲突
- 同一个 Pod 中的不同容器可以通过 localhost:port 进行通信
- 同一个 Pod 中的不同容器可以通过使用常规的进程间通信手段,例如 SystemV semaphores 或者 POSIX 共享内存
存储 Storage:
Pod 中可以定义一组共享的数据卷。Pod 中所有的容器都可以访问这些共享数据卷,以便共享数据。Pod 中数据卷的数据也可以存储持久化的数据,使得容器在重启后仍然可以访问到之前存入到数据卷中的数据。
不同 Pod 上的两个容器如果要通信,必须使用对方 Pod 的 IP 地址 + 对方容器的端口号进行网络通信
用户应该始终使用控制器来创建 Pod,而不是直接创建 Pod,控制器可以提供如下特性:
- 水平扩展(运行 Pod 的多个副本)
- rollout(版本更新)
- self-healing(故障恢复)
在 Kubernetes 中,广泛使用的控制器有:
- Deployment
- StatefulSet
- DaemonSet
控制器通过配置好的Pod Template信息来创建Pod。Pod 由控制器依据 Pod Template 创建以后,此时再修改 Pod Template 的内容,已经创建的 Pod 不会被修改。
Pod phase 代表其所处生命周期的阶段。
Phase | 描述 |
---|---|
Pending | Kubernetes 已经创建并确认该 Pod。此时可能有两种情况:Pod 还未完成调度(例如没有合适的节点)正在从 docker registry 下载镜像 |
Running | 该 Pod 已经被绑定到一个节点,并且该 Pod 所有的容器都已经成功创建。其中至少有一个容器正在运行,或者正在启动/重启 |
Succeeded | Pod 中的所有容器都已经成功终止,并且不会再被重启 |
Failed | Pod 中的所有容器都已经终止,至少一个容器终止于失败状态:容器的进程退出码不是 0,或者被系统 kill |
Unknown | 因为某些未知原因,不能确定 Pod 的状态,通常的原因是 master 与 Pod 所在节点之间的通信故障 |
Pod conditions描述Pod是否达到某些指定的条件。该数组的每一行可能有六个字段:
字段名 | 描述 |
---|---|
type | type 是最重要的字段,可能的取值有:PodScheduled: Pod 已被调度到一个节点Ready: Pod 已经可以接受服务请求,应该被添加到所匹配 Service 的负载均衡的资源池**Initialized:Pod 中所有初始化容器已成功执行Unschedulable:**不能调度该 Pod(缺少资源或者其他限制)**ContainersReady:**Pod 中所有容器都已就绪 |
status | 能的取值有:TrueFalseUnknown |
reason | Condition 发生变化的原因,使用一个符合驼峰规则的英文单词描述 |
message | Condition 发生变化的原因的详细描述,human-readable |
lastTransitionTime | Condition 发生变化的时间戳 |
lastProbeTime | 上一次针对 Pod 做健康检查/就绪检查的时间戳 |
**容器的检查(Probe)**是指kubelet周期性的检查容器的状况:
- 就绪检查 readinessProbe: 确定容器是否已经就绪并接收服务请求。如果就绪检查失败,kubernetes 将该 Pod 的 IP 地址从所有匹配的 Service 的资源池中移除掉。
- 健康检查 livenessProbe: 确定容器是否正在运行。如果健康检查失败,kubelete 将结束该容器,并根据 restart policy(重启策略)确定是否重启该容器。
当Pod被调度到节点上后,Kubelet开始使用容器引擎创建容器。容器可能有三种状态:
- Waiting: 容器的初始状态。处于 Waiting 状态的容器,仍然有对应的操作在执行,例如:拉取镜像、应用 Secrets等。
- Running: 容器处于正常运行的状态。容器进入 Running 状态之后,如果指定了 postStart hook,该钩子将被执行。
- Terminated: 容器处于结束运行的状态。容器进入 Terminated 状态之前,如果指定了 preStop hook,该钩子将被执行。
**重启策略:**Pod的重启测了默认是Always,kubelete 将在五分钟内,按照递延的时间间隔(10s, 20s, 40s ......)尝试重启已退出的容器,并在十分钟后再次启动这个循环,直到容器成功启动,或者 Pod 被删除。
通常,如果没有人或者控制器删除 Pod,Pod 不会自己消失。只有一种例外,那就是 Pod 处于 Scucceeded 或 Failed 的 phase,并超过了垃圾回收的时长(在 kubernetes master 中通过 terminated-pod-gc-threshold 参数指定),kubelet 自动将其删除。
初始化容器在工作容器启动之前执行,且:
- 初始化容器总是运行并自动结束
- kubelet 按顺序执行 Pod 中的初始化容器,前一个初始化容器成功结束后,下一个初始化容器才开始运行。所有的初始化容器成功执行后,才开始启动工作容器
- 如果 Pod 的任意一个初始化容器执行失败,kubernetes 将反复重启该 Pod,直到初始化容器全部成功(除非 Pod 的 restartPolicy 被设定为 Never)
- 初始化容器的 Resource request / limits 处理不同
- 初始化容器不支持readiness probe,因为初始化容器必须在 Pod ready 之前运行并结束
配置初始化容器:
apiVersion: v1
kind: Pod
metadata:
name: init-demo
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
volumeMounts:
- name: workdir
mountPath: /usr/share/nginx/html
# These containers are run during pod initialization
initContainers:
- name: install
image: busybox
command:
- wget
- "-O"
- "/work-dir/index.html"
- https://kuboard.cn
volumeMounts:
- name: workdir
mountPath: "/work-dir"
dnsPolicy: Default
volumes:
- name: workdir
emptyDir: {}
这个初始化容器的作用就是把https://kuboard.cn页面下载下来放到`/usr/share/nginx/html`下面。
kubectl get pod init-demo -o yaml
#查看初始化容器的日志
kubectl logs init-demo -c <init-container-1>
kubectl logs init-demo -c install
应用程序管理员可以为每一个应用程序创建 PodDisruptionBudget
对象(PDB)。PDB限制了多副本应用程序在自愿的毁坏情况发生时,最多有多少个副本可以同时停止。
集群管理员以及托管供应商在进行系统维护时,应该使用兼容 PodDisruptionBudget 的工具(例如 kubectl drain
)而不是直接删除 Pod 或者 Deployment。
PodDisruptionBudget
包含三个字段:
- 标签选择器
.spec.selector
用于指定 PDB 适用的 Pod。此字段为必填 .spec.minAvailable
:当完成驱逐时,最少仍然要保留多少个 Pod 可用。该字段可以是一个整数,也可以是一个百分比.spec.maxUnavailable
: 当完成驱逐时,最多可以有多少个 Pod 被终止。该字段可以是一个整数,也可以是一个百分比
且一个PDB对象只能同时指定minAvailable和maxUnavailable中的一个。二者可以指定为整数或百分比,百分比计算结果不是整数时会向上舍入。一般推荐使用 maxUnavailable
这种形式的定义。
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeper
#或者
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: zookeeper
kubectl apply -f xxx.yaml
kubectl get poddisruptionbudgets
kubectl get poddisruptionbudgets zk-pdb -o yaml
您应该始终通过创建 Controller 来创建 Pod,而不是直接创建 Pod。控制器可以提供如下特性:
- 水平扩展(运行 Pod 的多个副本)
- rollout(版本更新)
- self-healing(故障恢复) 例如:当一个节点出现故障,控制器可以自动地在另一个节点调度一个配置完全一样的 Pod,以替换故障节点上的 Pod。
ReplicaSet(副本集) 用来维护一个数量稳定的 Pod 副本集合,可以保证某种定义一样的 Pod 始终有指定数量的副本数在运行。Deployment对象包含 ReplicaSet 作为从属对象,并且可通过声明式、滚动更新的方式更新 ReplicaSet 及其 Pod。
因此请始终使用 Deployment,而不是 ReplicaSet
selector
: 用于指定哪些 Pod 属于该 ReplicaSet 的管辖范围replicas
: 副本数,用于指定该 ReplicaSet 应该维持多少个 Pod 副本template
: Pod模板,在 ReplicaSet 使用 Pod 模板的定义创建新的 Pod
Pod的ownerReference字段指向的对象即管理它的控制器。
下面是一个ReplicaSet的yaml,与Deployment类似:
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# modify replicas according to your case
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: nginx
image: nginx
直接创建的Pod如果labels匹配上了之前的ReplicaSet,也会受其管理。
#直接删除ReplicaSet及其Pod
kubectl delete
#只删除ReplicaSet
kubectl delete --cascade=false
Deployment 是最常用的用于部署无状态服务的方式。Deployment 控制器使得您能够以声明的方式更新 Pod(容器组)和 ReplicaSet(副本集)。
创建:
apiVersion: apps/v1 #与k8s集群版本有关,使用 kubectl api-versions 即可查看当前集群支持的版本
kind: Deployment #该配置的类型,我们使用的是 Deployment
metadata: #译名为元数据,即 Deployment 的一些基本属性和信息
name: nginx-deployment #Deployment 的名称
labels: #标签,可以灵活定位一个或多个资源,其中key和value均可自定义,可以定义多组,目前不需要理解
app: nginx #为该Deployment设置key为app,value为nginx的标签
spec: #这是关于该Deployment的描述,可以理解为你期待该Deployment在k8s中如何使用
replicas: 4 #使用该Deployment创建一个应用程序实例
selector: #标签选择器,与上面的标签共同作用,目前不需要理解
matchLabels: #选择包含标签app:nginx的资源
app: nginx
template: #这是选择或创建的Pod的模板
metadata: #Pod的元数据
labels: #Pod的标签,上面的selector即选择包含标签app:nginx的Pod
app: nginx
spec: #期望Pod实现的功能(即在pod中部署)
containers: #生成container,与docker中的container是同一种
- name: nginx #container的名称
image: nginx:latest #使用镜像nginx:1.8创建container,该container默认80端口可访问
#serviceAccountName: feng
不仅可以创建,因为是声明式的的方式,还可以更新。
kubectl apply -r nginx-deployment.yaml
#可以增加--record选项,来记录版本,可以用来回滚和或回顾某一个 Deployment版本变化的原
--record
#查看deployment的状态
kubectl rollout status -r nginx-deployment.yaml
更新:
#直接修改该deployment,但本地文件nginx-deployment.yaml不会更改。
kubectl edit -f nginx-deployment.yaml
#或者修改本地文件nginx-deployment.yaml后再apply
当且仅当 Deployment 的 Pod template(
.spec.template
)字段中的内容发生变更时(例如标签、容器的镜像被改变),Deployment 的发布更新(rollout)将被触发。Deployment 中其他字段的变化(例如修改 .spec.replicas 字段)将不会触发 Deployment 的发布更新(rollout)
- Deployment 将确保更新过程中,任意时刻只有一定数量的 Pod 被关闭。默认情况下,Deployment 确保至少
.spec.replicas
的 75% 的 Pod 保持可用(25% max unavailable) - Deployment 将确保更新过程中,任意时刻只有一定数量的 Pod 被创建。默认情况下,Deployment 确保最多
.spec.replicas
的 25% 的 Pod 被创建(25% max surge)
回滚:
#查看历史版本
kubectl rollout history deployment.v1.apps/nginx-deployment
#查看version2的详细信息
kubectl rollout history deployment.v1.apps/nginx-deployment --revision=2
#回滚到前一个版本
kubectl rollout undo deployment.v1.apps/nginx-deployment
#回滚到指定版本
kubectl rollout undo deployment.v1.apps/nginx-deployment --to-revision=2
伸缩:
kubectl scale deployment.v1.apps/nginx-deployment --replicas=10
暂停和继续:
可以先暂停 Deployment,然后再触发一个或多个更新,最后再继续(resume)该 Deployment。这种做法使得您可以在暂停和继续中间对 Deployment 做多次更新,而无需触发不必要的滚动更新。
#暂停Deployment
kubectl rollout pause deployment.v1.apps/nginx-deployment
#进行多次更新
xxxx
#继续Deployment
kubectl rollout resume deployment.v1.apps/nginx-deployment
可以执行命令 kubectl rollout status
检查 Deployment 是否已经处于 complete 状态。如果是,则该命令的退出码为 0。
kubectl rollout status deployment.v1.apps/nginx-deployment
echo $?
清理策略:
通过 Deployment 中 .spec.revisionHistoryLimit
字段,可指定为该 Deployment 保留多少个旧的 ReplicaSet。超出该数字的将被在后台进行垃圾回收。该字段的默认值是 10。
如果该字段被设为 0, Kubernetes 将清理掉该 Deployment 的所有历史版本(revision),因此,您将无法对该 Deployment 执行回滚操作 kubectl rollout undo
。
部署策略:
通过 Deployment 中 .spec.strategy
字段,可以指定使用 滚动更新 RollingUpdate
的部署策略还是使用 重新创建 Recreate
的部署策略:
strategy:
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
type: RollingUpdate
maxSurge为最大超出副本数,即滚动更新过程中,可以超出期望副本数的最大值。
maxUnavailable为最大不可用副本数,即滚动更新过程中,不可用副本数的最大值。
Deployment编排的是无状态的应用,一个应用的所有Pod是完全一样。
StatefulSet用于管理有状态的应用程序,适用于:
- 稳定、唯一的网络标识(dnsname)
- 每个Pod始终对应各自的存储路径(PersistantVolumeClaimTemplate,PVC)
- 按顺序地增加副本、减少副本,并在减少副本时执行清理
- 按顺序自动地执行滚动更新
下面是创建StatefulSet的例子:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 3 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx:1.7.9
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi
第一部分是名为nginx的Headless Service。普通的Service
都有ClusterIP
,它其实就是一个虚拟IP
,会把请求转发到该Service
所代理的某一个Pod
上。而Headless Service的yaml中clusterIP为None,它会为代理的每一个StatefulSet
创建出来的Endpoint
也就是Pod
添加DNS域名解析,这样Pod
之间就可以相互访问。(假设一个 StatefulSet 的副本数为 N,其中的每一个 Pod 都会被分配一个序号,序号的取值范围从 0 到 N - 1)
- StatefulSet 中 Pod 的 hostname 格式为
$(StatefulSet name)-$(Pod 序号)
。上面的例子将要创建三个 Pod,其名称分别为: web-0,web-1,web-2。 - StatefulSet 可以使用 Headless Service 来控制其 Pod 所在的域。该域(domain)的格式为
$(service name).$(namespace).svc.cluster.local
,其中 “cluster.local” 是集群的域。 - StatefulSet 中每一个 Pod 将被分配一个 dnsName,格式为:
$(podName).$(所在域名)
例如StatefulSet的name为web,service的name为nginx,namespace为default,因此
Pod的name为web-n
,StatefulSet domain为nginx.default.svc.cluster.local
,每个Pod的dnsName为web-n.nginx.default.svc.cluster.local
。这个Pod的dnsName就是分配给Pod的固定唯一网络标识。
第二部分是StatefulSet,与Deployment基本一致,唯一的区别在于要编写volumeClaimTemplates
,这部分是为了稳定的存储。
持久卷(PV)是集群中的一块存储,它是集群资源。PVC(持久卷申领)表达的是Pod对存储的请求,PVC会申领PV资源。
因此通过在StatefulSet的yaml中编写volumeClaimTemplates,被这个StatefulSet管理的Pod,都会声明一个对应的PVC用来获取PV资源。创建出来的PVC的命名格式是PVC名-StatefulSet名-序号。当一个Pod重新调度到其他节点上时,这个新Pod的还是会查找到之前的PVC。由于PVC
的生命周期是独立于使用它的Pod
的,这样新Pod
就接管了以前旧Pod
留下的数据。
因此,StatefulSet 中的 Pod 具备一个唯一标识,该标识由以下几部分组成:
- 序号
- 稳定的网络标识
- 稳定的存储
该标识始终与 Pod 绑定,无论该 Pod 被调度(重新调度)到哪一个节点上。
部署按序号从0到n-1创建,删除且按相反的顺序,新增pod时前序pod必须处于Running(运行)和 Ready(就绪)的状态,终止和删除pod的时候该pod后序pod必须全部已终止。
指定.spec.podManagementPlicy为Parallel的话,就会同时创建或终止所有Pod,但是只影响伸缩操作,不影响更新。
StatefulSet默认的更新策略是RollingUpdate,StatefulSet会自动删除并重建每一个Pod,从序号最大到序号最小,且只有当正在更新的Pod达到了Running 和 Ready 的状态之后,才继续更新其前序 Pod。
该策略存在问题,即如果Pod始终不能进入 Running 和 Ready 的状态,StatefulSet 将停止滚动更新并一直等待,因此此时需要修复Pod template并且删掉所有已经尝试使用有问题的 Pod template 的 Pod。StatefulSet此时才会开始使用修复了的 Pod template 重建 Pod。(Forced Rollback策略可以解决?)
Daemon是守护进程。守护进程是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或循环等待处理某些事件的发生;它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。
DaemonSet 控制器确保所有(或一部分)的节点都运行了一个指定的 Pod 副本。
- 每当向集群中添加一个节点时,指定的 Pod 副本也将添加到该节点上
- 当节点从集群中移除时,Pod 也就被垃圾回收了
- 删除一个 DaemonSet 可以清理所有由其创建的 Pod
典型的使用场景有在每个节点上运行集群的存储守护进程、日志收集守护进程或者监控守护进程。
创建示例yaml:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: fluentd-elasticsearch
namespace: kube-system
labels:
k8s-app: fluentd-logging
spec:
selector:
matchLabels:
name: fluentd-elasticsearch
template:
metadata:
labels:
name: fluentd-elasticsearch
spec:
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
containers:
- name: fluentd-elasticsearch
image: fluent/fluentd-kubernetes-daemonset:v1.7.1-debian-syslog-1.0
resources:
limits:
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi
volumeMounts:
- name: varlog
mountPath: /var/log
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
.spec.selector
是必填字段,且指定该字段时,必须与 .spec.template.metata.labels
字段匹配。
如果指定.spec.template.spec.nodeSelector将只在指定的节点上创建Pod。
Kubernetes v1.12 版本以后,默认通过 kubernetes 调度器来调度 DaemonSet 的 Pod。
例子,计算pi:
apiVersion: batch/v1
kind: Job
metadata:
name: pi
spec:
template:
spec:
containers:
- name: pi
image: perl
command: ["perl", "-Mbignum=bpi", "-wle", "print bpi(2000)"]
restartPolicy: Never
backoffLimit: 4
kubectl apply -f pi-job.yaml
kubectl get pods
kubectl logs pi-rh6kb
有三种主要的任务类型适合使用 Job 运行:
- Non-parallel Jobs
- 通常,只启动一个 Pod,除非该 Pod 执行失败
- Pod 执行成功并结束以后,Job 也立刻进入完成 completed 状态
- Parallel Jobs with a fixed completion count
.spec.completions
为一个非零正整数- Job 将创建至少
.spec.completions
个 Pod,编号为 1 -.spec.completions
尚未实现 - Job 记录了任务的整体执行情况,当 1 -
.spec.completions
中每一个编号都有一个对应的 Pod 执行成功时,Job 进入完成状态
- Parallel Jobs with a work queue
- 不指定
.spec.completions
,使用.spec.parallelism
- Pod 之间必须相互之间自行协调并发,或者使用一个外部服务决定每个 Pod 各自执行哪些任务。例如,某个Pod可能从带工作队列(work queue)中取出最多N个条目的批次数据
- 每个 Pod 都可以独立判断其他同僚(peers)是否完成,并确定整个Job是否完成
- 当 Job 中任何一个 Pod 成功结束,将不再为其创建新的 Pod
- 当所有的 Pod 都结束了,且至少有一个 Pod 执行成功后才结束,则 Job 判定为成功结束
- 一旦任何一个 Pod 执行成功并退出,Job 中的任何其他 Pod 都应停止工作和输出信息,并开始终止该 Pod 的进程
- 不指定
并发数.spec.parallelism
如果不设置默认为1,设置为0则该job被暂停。
restartPolicy是指当前的重启策略。对于 Job,只能设置为 Never
或者 OnFailure
。
- always:当容器退出时,总是重启容器,默认策略
- onfailure:当容器异常退出(退出状态码非0)时,重启容器
- nerver:当容器退出时,从不重启容器
.spec.backoffLimit
来设定 Job 最大的重试次数。该字段的默认值为 6.
Job中也可以设置.spec.activeDeadlineSeconds
,该字段限定了Job对象的最大存活时间,且该字段优先级高于.spec.backoffLimit。此外,Job 中有两个 activeDeadlineSeconds: .spec.activeDeadlineSeconds
和 .spec.template.spec.activeDeadlineSeconds
,后者是Pod的最大存活时间,不是Job的。
Job可以由TTL机制自动清理已结束的Job(Completed或Finished)。
指定.spec.ttlSecondsAfterFinished
,其值决定了在Job结束多少秒后自动删除。如果设置为0则结束后立即删除,如果没设置则TTL控制器不会清理该Job。
在创建 Job 时,系统默认将为其指定一个 .spec.selector
的取值,并确保其不会与任何其他 Job 重叠。在少数情况下,您仍然可能需要覆盖这个自动设置 .spec.selector
的取值。在做此项操作时,您必须十分小心。
CronJob 按照预定的时间计划(schedule)创建Job。一个 CronJob 对象类似于 crontab (cron table) 文件中的一行记录。该对象根据 Cron (opens new window)格式定义的时间计划,周期性地创建 Job 对象。所有 CronJob 的 schedule
中所定义的时间,都是基于 master 所在时区来进行计算的。
例子:
apiVersion: batch/v1
kind: CronJob
metadata:
name: hello
spec:
schedule: "*/1 * * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: hello
image: busybox
args:
- /bin/sh
- -c
- date; echo Hello from the Kubernetes cluster
restartPolicy: OnFailure
每分钟创建一个Job,Job的pod输出date信息并打印一行话。
kubectl apply -f cronjob.yaml
kubectl get cronjobs
kubectl get jobs
kubectl get pods
kubectl logs hello-28310707-7698x
Mon Oct 30 05:07:02 UTC 2023
Hello from the Kubernetes cluster
kubectl delete cronjob hello
.spec.startingDeadlineSeconds
为可选字段,代表着从计划的时间点开始,最迟多少秒之内必须启动 Job。如果超过了这个时间点,CronJob 就不会为其创建 Job,并将其记录为一次错过的执行次数。如果该字段未指定,则 Job 必须在指定的时间点执行。
.spec.concurrencyPolicy
是选填字段,指定了如何控制该 CronJob 创建的 Job 的并发性。为Allow(默认值)允许并发运行Job,为Forbid时不允许,新执行点到时且上个Job未执行完,则跳过新的执行时间点,保留仍在运行的 Job。为Replace时,将创建新Job替代正在执行的Job。
某些 Kubernetes 对象是其他 Kubernetes 对象的所有者(owner),同时,我们称被拥有的对象为拥有者的从属对象(dependent)。每一个从属对象都有一个 metadata.ownerReferences
字段,标识其拥有者是哪一个对象。
在 Kubernetes 的设计里,跨名称空间的所有者-从属对象的关系是不被允许的。这意味着:
- 名称空间内的从属对象只能指定同名称空间的对象作为其所有者
- 集群级别的对象只能指定集群级别的对象作为其所有者
当删除某个对象时,可以指定该对象的从属对象是否同时被自动删除(级联删除cascading deletion)。级联删除有两种模式,后台(background)和前台(foreground)
后台类似于先删除所有者对象,并由垃圾回收器在后台删除其从属对象。
前台类似于垃圾回收器先删除从属对象,再删除所有者对象(比较复杂)
--cascade=true #级联删除
--cascade=false #不级联删除 orphan
Service 是 Kubernetes 中的一种服务发现机制:
- Pod 有自己的 IP 地址
- Service 被赋予一个唯一的 dns name
- Service 通过 label selector 选定一组 Pod
- Service 实现负载均衡,可将请求均衡分发到选定这一组 Pod 中
Service也可以创建无label selector的,但是需要自己创建Endpoints对象。这种场景适用于访问集群外部或者另一个名称空间的Service:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Endpoints
metadata:
name: my-service
subsets:
- addresses:
- ip: 192.0.2.42
ports:
- port: 9376
Kubernetes 集群中的每个节点都运行了一个 kube-proxy
,负责为 Service(ExternalName 类型的除外)提供虚拟 IP 访问。
Kubernetes 支持三种 proxy mode(代理模式),他们的版本兼容性如下:
代理模式 | Kubernetes 版本 | 是否默认 |
---|---|---|
User space proxy mode | v1.0 + | |
Iptables proxy mode | v1.1 + | 默认 |
Ipvs proxy mode | v1.8 + |
在 iptables proxy mode 下:
- kube-proxy 监听 kubernetes master 以获得添加和移除 Service / Endpoint 的事件
- kube-proxy 在其所在的节点(每个节点都有 kube-proxy)上为每一个 Service 安装 iptable 规则
- iptables 将发送到 Service 的 ClusterIP / Port 的请求重定向到 Service 的后端 Pod 上
- 对于 Service 中的每一个 Endpoint,kube-proxy 安装一个 iptable 规则
- 默认情况下,kube-proxy 随机选择一个 Service 的后端 Pod
但是:
- IPVS 代理模式可以比 iptables 代理模式有更低的网络延迟,在同步代理规则时,也有更高的效率
- 与 user space 代理模式 / iptables 代理模式相比,IPVS 模式可以支持更大的网络流量
Kubernetes中,Serbice对象也可以定义多个端口:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
- name: https
protocol: TCP
port: 443
targetPort: 9377
Kubernetes 中可以通过不同方式发布 Service,通过 ServiceType
字段指定,该字段的默认值是 ClusterIP
,可选值有:
- ClusterIP: 默认值。通过集群内部的一个 IP 地址暴露 Service,只在集群内部可以访问
- NodePort: 通过每一个节点上的的静态端口(NodePort)暴露 Service,同时自动创建 ClusterIP 类型的访问方式
- 在集群内部通过
$(ClusterIP): $(Port)
访问 - 在集群外部通过
$(NodeIP): $(NodePort)
访问
- 在集群内部通过
- LoadBalancer: 通过云服务供应商(AWS、Azure、GCE 等)的负载均衡器在集群外部暴露 Service,同时自动创建 NodePort 和 ClusterIP 类型的访问方式
- 在集群内部通过
$(ClusterIP): $(Port)
访问 - 在集群外部通过
$(NodeIP): $(NodePort)
访问 - 在集群外部通过
$(LoadBalancerIP): $(Port)
访问
- 在集群内部通过
- ExternalName: 将 Service 映射到
externalName
指定的地址(例如:foo.bar.example.com),返回值是一个 CNAME 记录。不使用任何代理机制。
快速暴露Deployment:
kubectl expose deployment nginx --port=80
Service的A 记录:
- Service(headless Service 除外)将被分配一个 DNS A 记录,格式为
my-svc.my-namespace.svc.cluster-domain.example
。该 DNS 记录解析到 Service 的 ClusterIP。 - Headless Service(没有 ClusterIP)也将被分配一个 DNS A 记录,格式为
my-svc.my-namespace.svc.cluster-domain.example
。该 DNS 记录解析到 Service 所选中的一组 Pod 的 IP 地址的集合。调用者应该使用该 IP 地址集合,或者按照轮询(round-robin)的方式从集合中选择一个 IP 地址使用。
Pod 定义中还有一个可选字段 spec.subdomain
可用来指定 Pod 的 subdomain。例如,名称空间 my-namespace
中,某 Pod 的 hostname 为 foo
,并且 subdomain 为 bar
,则该 Pod 的完整域名(FQDN)为 foo.bar.my-namespace.svc.cluster-domain.example
。
(没懂很抽象)
也nginx服务为例,当Service发布到互联网上时,为确保通信渠道的安安全,需要:
- 准备 https 证书(购买,或者自签名)
- 将该 nginx 服务配置好,并使用该 https 证书
- 配置 Secret,以使得其他 Pod 可以使用该证书
首先创建密钥对,并读取其中的内容base64编码
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout ./nginx-https/nginx.key -out ./nginx-https/nginx.crt -subj "/CN=my-nginx/O=my-nginx"
创建Secret,填入base64编码后的内容并创建Secret
apiVersion: "v1"
kind: "Secret"
metadata:
name: "nginxsecret"
namespace: "default"
data:
nginx.crt: "xxx"
nginx.key: "xxx"
创建Service,类型是NodePort,暴露了443端口,没指定nodePort将会随机,并通过挂载卷将Secret中的证书挂载到nginx的ssl目录下。
apiVersion: v1
kind: Service
metadata:
name: my-nginx
labels:
run: my-nginx
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
- port: 443
protocol: TCP
name: https
selector:
run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx
spec:
selector:
matchLabels:
run: my-nginx
replicas: 1
template:
metadata:
labels:
run: my-nginx
spec:
volumes:
- name: secret-volume
secret:
secretName: nginxsecret
containers:
- name: nginxhttps
image: bprashanth/nginxhttps:1.0
ports:
- containerPort: 443
- containerPort: 80
volumeMounts:
- mountPath: /etc/nginx/ssl
name: secret-volume
创建:
kubectl apply -f nginx-secure-app.yaml
之后使用命令就可以访问到nginx服务(32572是随机生成的nodePort)
curl -k https://node-ip:32572/
(但是对于http、https形式的访问更推荐使用Ingress替代。)
参考https://zhuanlan.zhihu.com/p/618328589
Ingress是一种Kubernetes资源,用于将外部流量路由到Kubernetes集群内的服务。与NodePort相比,它提供了更高级别的路由功能和负载平衡,可以根据HTTP请求的路径、主机名、HTTP方法等来路由流量。它本身不会直接处理或转发流量,而是需要配合一个 Ingress 控制器来实现。
Ingress Controller是一个独立的组件,它会监听 Kubernetes API 中的 Ingress 资源变化,并根据定义的路由规则配置负载均衡器、反向代理或其他网络代理,从而实现外部流量的转发。因此,可以将 Ingress 控制器视为 Ingress 资源的实际执行者
在 Kubernetes 中,有很多不同的 Ingress 控制器可以选择,例如 Nginx、Traefik、HAProxy、Envoy 等等。
下面以ingress-nginx举例:
kubectl get pods -n ingress-nginx
NAME READY STATUS RESTARTS AGE
ingress-nginx-admission-create-kp8kt 0/1 Completed 0 21m
ingress-nginx-admission-patch-qm7c4 0/1 Completed 0 21m
ingress-nginx-controller-7bb79cdb74-lbq55 1/1 Running 0 21m
- ingress-nginx-controller是Ingress-nginx的控制器组件,它负责监视Kubernetes API server上的Ingress对象,并根据配置动态地更新Nginx配置文件,实现HTTP(S)的负载均衡和路由。
- ingress-nginx-admission-create和ingress-nginx-admission-patch都是Kubernetes Admission Controller,它们不是一直处于运行状态的容器,而是根据需要动态地生成和销毁。这些Admission Controller在Kubernetes中以Deployment的方式进行部署,因此,它们的Pod将根据副本数创建多个副本,并根据负载和需要动态地生成和销毁。
kubectl get svc -n ingress-nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ingress-nginx-controller NodePort 10.96.34.24 <none> 80:30080/TCP,443:30443/TCP 21m
ingress-nginx-controller-admission ClusterIP 10.96.25.38 <none> 443/TCP 21m
- ingress-nginx-controller Service:这个Service负责将请求转发到ingress-nginx-controller Pods。它通常会将流量分发到ingress-nginx-controller的多个副本中,并确保副本集的负载平衡。这个Service可以被配置为使用NodePort、LoadBalancer或ClusterIP类型,根据需要进行暴露
- ingress-nginx-controller-admission Service:这个Service是用于 Kubernetes Admission Webhooks 的,允许在创建、更新或删除资源时,对其进行校验或修改。它提供了一个API Endpoint,用于与 Kubernetes API Server 进行通信,以便进行这些校验或修改。该Service也可以被配置为使用NodePort、LoadBalancer或ClusterIP类型,根据需要进行暴露。
安装Nginx Ingress Controller:
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.3/deploy/static/provider/cloud/deploy.yaml
mv deploy.yaml ingress-nginx-controller.yaml
#修改其中的镜像和Service的信息:
anjia0532/google-containers.ingress-nginx.kube-webhook-certgen:v20231011-8b53cabe0
anjia0532/google-containers.ingress-nginx.controller:v1.9.3
351 ports:
352 - appProtocol: http
353 name: http
354 port: 80
355 protocol: TCP
356 targetPort: http
357 nodePort: 30080
358 - appProtocol: https
359 name: https
360 port: 443
361 protocol: TCP
362 targetPort: https
363 nodePort: 30443
364 selector:
365 app.kubernetes.io/component: controller
366 app.kubernetes.io/instance: ingress-nginx
367 app.kubernetes.io/name: ingress-nginx
368 type: NodePort
#安装
kubectl apply -f ingress-nginx-controller.yaml
利用之前配置的nginx-service,编写ingress资源对象:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: test-nginx
spec:
ingressClassName: nginx
rules:
- host: "test.feng"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-nginx
port:
number: 80
ingressClassName指定ingress控制器是nginx,配置上使用前缀路径匹配,backend字段用来指定要将流量转发到哪个Service,这里转发到my-nginx的80端口。
创建好之后在node里配置好/etc/hosts
,然后访问:
root@k8s-worker:/# curl http://test.feng:30080/
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
可以成功访问。
关于https的配置以及Ingress Controller的更多暴露方式,参考https://zhuanlan.zhihu.com/p/618328589
通过.spec.hostnames
字段来向Pod的/etc/hosts
文件添加额外的条目:
apiVersion: v1
kind: Pod
metadata:
name: hostaliases-pod
spec:
restartPolicy: Never
hostAliases:
- ip: "127.0.0.1"
hostnames:
- "foo.local"
- "bar.local"
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
containers:
- name: cat-hosts
image: busybox
command:
- cat
args:
- "/etc/hosts"
TODO
Kubernetes 中,Network Policy(网络策略)定义了一组 Pod 是否允许相互通信,或者与网络中的其他端点 endpoint 通信。
Network Policy 由网络插件实现,因此,您使用的网络插件必须能够支持 NetworkPolicy
才可以使用此特性。如果您仅仅是创建了一个 Network Policy 对象,但是您使用的网络插件并不支持此特性,您创建的 Network Policy 对象是不生效的。
Network Police 不会相互冲突,是相互叠加的(并集)
例如:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
入站的流量允许下面三种的任何一种且必须访问的是Pod的6379端口:
- ip来自172.17.0.0/16且不属于172.17.1.0/24
- namespace标签为project=myproject
- pod标签为role=frontent
出战的流量目标必须为10.0.0.0/24且目标的端口为5978。
可指定的标签选择器有4种:
- podSelector 选择与
NetworkPolicy
同名称空间中的 Pod 作为入方向访问控制规则的源或者出方向访问控制规则的目标 - namespaceSelector 选择某个名称空间(其中所有的Pod)作为入方向访问控制规则的源或者出方向访问控制规则的目标
- namespaceSelector 和 podSelector 在一个
to
/from
条目中同时包含namespaceSelector
和podSelector
将选中指定名称空间中的指定 Pod。 - ipBlock 可选择 IP CIDR 范围作为入方向访问控制规则的源或者出方向访问控制规则的目标。这里应该指定的是集群外部的 IP,因为集群内部 Pod 的 IP 地址是临时分配的,且不可预测。
第三种的示例如下,podSelector前面并没有--
:
...
ingress:
- from:
- namespaceSelector:
matchLabels:
user: alice
podSelector:
matchLabels:
role: client
...
一些策略的例子:
拒绝当前namespace的所有入方向流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
允许当前namespace的所有入方向流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
spec:
podSelector: {}
ingress:
- {}
policyTypes:
- Ingress
允许当前namespace的所有出方向流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
spec:
podSelector: {}
egress:
- {}
policyTypes:
- Egress
拒绝当前namespace的所有入和出方向流量:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Kubernetes Volume(数据卷)主要解决了如下两方面问题:
- 数据持久性:通常情况下,容器运行起来之后,写入到其文件系统的文件暂时性的。当容器崩溃后,kubelet 将会重启该容器,此时原容器运行后写入的文件将丢失,因为容器将重新从镜像创建。
- 数据共享:同一个 Pod(容器组)中运行的容器之间,经常会存在共享文件/文件夹的需求
在 Kubernetes 里,Volume(数据卷)存在明确的生命周期(与包含该数据卷的容器组相同)。因此,Volume(数据卷)的生命周期比同一容器组中任意容器的生命周期要更长,不管容器重启了多少次,数据都能被保留下来。当然,如果容器组退出了,数据卷也就自然退出了。此时,根据容器组所使用的 Volume(数据卷)类型不同,数据可能随数据卷的退出而删除,也可能被真正持久化,并在下次容器组重启时仍然可以使用。
- emptyDir。emptyDir类型的数据卷在容器组被创建时分配给该容器组,并且直到容器组被移除,该数据卷才被释放。该数据卷初始分配时,始终是一个空目录。同一容器组中的不同容器都可以对该目录执行读写操作,并且共享其中的数据。当容器组被移除时,emptyDir数据卷中的数据将被永久删除
- nsf。nfs 类型的数据卷可以加载 NFS(Network File System)到您的容器组/容器。容器组被移除时,将仅仅 umount(卸载)NFS 数据卷,NFS 中的数据仍将被保留。
- cephfs。cephfs 数据卷使得您可以挂载一个外部 CephFS 卷到您的容器组中。对于 kubernetes 而言,cephfs 与 nfs 的管理方式和行为完全相似,适用场景也相同。不同的仅仅是背后的存储介质。
- hostPath。hostPath 类型的数据卷将 Pod(容器组)所在节点的文件系统上某一个文件或文件夹挂载进容器组(容器)。
- configMap。ConfigMap 提供了一种向容器组注入配置信息的途径。ConfigMap 中的数据可以被 Pod(容器组)中的容器作为一个数据卷挂载。
- secret。secret 数据卷可以用来注入敏感信息(例如密码)到容器组。您可以将敏感信息存入 kubernetes secret 对象,并通过 Volume(数据卷)以文件的形式挂载到容器组(或容器)。secret 数据卷使用 tmpfs(基于 RAM 的文件系统)挂载。
- persistentVolumeClaim。数据卷用来挂载 PersistentVolume 存储卷。PersistentVolume 存储卷为用户提供了一种在无需关心具体所在云环境的情况下”声明“ 所需持久化存储的方式。
使用 volumeMounts.subPath
属性,可以使容器在挂载数据卷时指向数据卷内部的一个子路径,而不是直接指向数据卷的根路径。
挂载是指将定义在 Pod 中的数据卷关联到容器,同一个 Pod 中的同一个数据卷可以被挂载到该 Pod 中的多个容器上
容器对挂载的数据卷是否具备读写权限,如果 readOnly
为 true
,则只读,否则可以读写(为 false
或者不指定)。默认为 false
数据卷的挂载传播(Mount Propagation)由 Pod 的 spec.containers[*].volumeMounts.mountPropagation
字段控制。可选的取值有:
- None: 默认值。在数据卷被挂载到容器之后,此数据卷不会再接受任何后续宿主机或其他容器挂载到该数据卷对应目录下的子目录的挂载。同样的,在容器中向该数据卷对应目录挂载新目录时,宿主机也不能看到。对应 Linux 的
private
mount propagation 选项 - HostToContainer:在数据卷被挂载到容器之后,宿主机向该数据卷对应目录添加挂载时,对容器是可见的。对应 Linux 的
rslave
mount propagation 选项 - Bidirectional:在数据卷被挂载到容器之后,宿主机向该数据卷对应目录添加挂载时,对容器是可见的;同时,从容器中向该数据卷创建挂载,同样也对宿主机可见。对应 Linux 的
rshared
mount propagation 选项
PersistentVolume有时候也叫存储卷。相关主要是PV、PVC和storeclass等概念。
PersistentVolume(PV 存储卷)是集群中的一块存储空间(集群的资源),由集群管理员管理、或者由 Storage Class(存储类)自动管理。
PersistentVolumeClaim(存储卷声明)是一种类型的 Volume(数据卷),PersistentVolumeClaim(存储卷声明)引用的 PersistentVolume(存储卷)有自己的生命周期,该生命周期独立于任何使用它的容器组。PersistentVolumeClaim(PVC 存储卷声明)代表用户使用存储的请求
概念间的关系:
- PersistentVolume 是集群中的存储资源,通常由集群管理员创建和管理
- StorageClass 用于对 PersistentVolume 进行分类,如果正确配置,StorageClass 也可以根据 PersistentVolumeClaim 的请求动态创建 Persistent Volume
- PersistentVolumeClaim 是使用该资源的请求,通常由应用程序提出请求,并指定对应的 StorageClass 和需求的空间大小
- PersistentVolumeClaim 可以做为数据卷的一种,被挂载到容器组/容器中使用
有两种方式为PVC提供PV:静态和动态。静态即集群管理员创建好一系列的PV,动态即配置了合适的storageclass且PVC关联了该SC的情况下,集群可动态创建PV。
使用中保护:
- 使用中保护(Storage Object in Use Protection)的目的是确保正在被容器组使用的 PVC 以及其绑定的 PV 不能被系统删除,以避免可能的数据丢失。
- 如果用户删除一个正在使用中的 PVC,则该 PVC 不会立即被移除掉,而是推迟到该 PVC 不在被任何容器组使用时才移除;同样的如果管理员删除了一个已经绑定到 PVC 的 PV,则该 PV 也不会立刻被移除掉,而是推迟到其绑定的 PVC 被删除后才移除掉。
PV的回收策略:
- 保留Retain,当绑定的PVC 被删除后PV仍然存在但是无法被其他PVC绑定,需要手动删除。
- 删除Delete。删除策略将从 kubernete 集群移除 PersistentVolume 以及其关联的外部存储介质(云环境中的 AWA EBS、GCE PD、Azure Disk 或 Cinder volume)。
- 再利用Recycle。PV回收时执行一个基本的清楚操作使其可以再次被PVC绑定。(已被废弃,用动态制备来取代)
- 非持久性存储
- emptyDir
- HostPath (只在单节点集群上用做测试目的)
- 网络连接性存储
- SAN:iSCSI、ScaleIO Volumes、FC (Fibre Channel)
- NFS:nfs,cfs
- 分布式存储
- Glusterfs
- RBD (Ceph Block Device)
- CephFS
- Portworx Volumes
- Quobyte Volumes
- 云端存储
- GCEPersistentDisk
- AWSElasticBlockStore
- AzureFile
- AzureDisk
- Cinder (OpenStack block storage)
- VsphereVolume
- StorageOS
- 自定义存储
- FlexVolume
- 不推荐
- Flocker (最近更新2016年 https://github.com/ClusterHQ/flocker/)
尽管 PersistentVolumeClaim 允许用户消耗抽象的存储资源, 常见的情况是针对不同的问题用户需要的是具有不同属性(如,性能)的 PersistentVolume 卷。 集群管理员需要能够提供不同性质的 PersistentVolume, 并且这些 PV 卷之间的差别不仅限于卷大小和访问模式,同时又不能将卷是如何实现的这些细节暴露给用户。 为了满足这类需求,就有了存储类(StorageClass) 资源。
绑定模式:
- Immediate,PVC创建后,立刻动态创建PV并将其绑定到PVC
- WaitForFirstConsumer,直到PVC第一次被容器组使用时,才创建PV,并将其绑定到PVC。
LocalPV:Kubernetes直接使用宿主机的本地磁盘目录 ,来持久化存储容器的数据。它的读写性能相比于大多数远程存储来说,要好得多,尤其是SSD盘。
创建PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 1Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /tmp/pvdir
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker
创建storeclass:
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
创建一个StatefulSet服务:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "nginx"
replicas: 1 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-storage"
resources:
requests:
storage: 1Gi
查看pvc和pv的状态:
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound example-pv 1Gi RWO local-storage 9m50s
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 1Gi RWO Delete Bound default/www-web-0 local-storage 14m
都已经绑定成功。往挂载的目录下写个文件:
echo 1 > index.html
curl http://10.244.1.27/
1
参考一下Z3ratu1学长的:https://blog.z3ratu1.top/k8s%E5%85%A5%E9%97%A8.html
首先制作一个PV作为nsf server的volume:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /tmp/nfsdir
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker
然后起一个nsf server的pod,后面又写了一个Service(没啥用,接下来还是直接用pod的ip了)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nfs-server
spec:
replicas: 1
selector:
matchLabels:
app: nfs-server
template:
metadata:
labels:
app: nfs-server
spec:
nodeSelector:
kubernetes.io/hostname: k8s-worker
containers:
- name: nfs-server
image: itsthenetwork/nfs-server-alpine:12
imagePullPolicy: IfNotPresent
ports:
- name: nfs
containerPort: 2049
- name: mountd
containerPort: 20048
- name: rpcbind
containerPort: 111
securityContext:
privileged: true
env:
- name: SHARED_DIRECTORY
value: /tmp/nfsdir
volumeMounts:
- name: upload-data
mountPath: /tmp/nfsdir
volumeClaimTemplates:
- metadata:
name: upload-data
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 2Gi
---
apiVersion: v1
kind: Service
metadata:
name: nfs-server-service
labels:
app: nfs-server
spec:
ports:
- port: 2049
name: nfs
- port: 20048
name: mountd
- port: 111
name: rpcbind
clusterIP: None
selector:
app: nfs-server
这时候相当于NFS Server就已经搭建完成,接下来制作一个NFS类型的PV:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-volume
spec:
storageClassName: "nfs"
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.29
#server: nfs-server.default.svc.cluster.local
path: "/"
storageClassName似乎可以并不实际存在,和接下来的PVC中的storageClassName相同即可。10.244.1.29是Pod的Ip。
制作PVC:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-claim
spec:
volumeName: nfs-volume
storageClassName: "nfs"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
启动一个Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-app
spec:
replicas: 1
selector:
matchLabels:
app: nfs-nginx
template:
metadata:
labels:
app: nfs-nginx
spec:
containers:
- name: nginx-nfs
image: nginx:1.9.5
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
volumeMounts:
- mountPath: /usr/share/nginx/html
name: nfs-volume
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-claim
再进去目录看看,发现挂载成功
root@nginx-app-78c859f8b-z9mgj:/usr/share/nginx/html# cat index.html
1
需要注意的是volumeClaimTemplates只能在statefulSet里面用,而且如果nfs服务器起的是headless service,
另外一个问题是Pod的PV那里用的nfs pod的ip,因为是headless serbice正常应该用域名访问,尝试了确实不行,似乎云环境才行?不是很懂,非常奇怪。
Kubernetes ConfigMap 可以将配置信息和容器镜像解耦,以使得容器化的应用程序可移植。
ConfigMap 与Secret类似, 但是提供的是一种处理不含敏感信息的字符串的方法。 用户和系统组件都可以在 ConfigMap 中存储配置数据。
使用kubectl create configmap
创建:
kubectl create configmap <映射名称> <数据源>
kubectl create configmap game-config --from-file=./
kubectl create configmap game-config-2 --from-file=./game.properties
kubectl create configmap game-config-2 --from-file=./game.properties --from-file=./ui.properties
#基于env文件创建ConfigMap
kubectl create configmap game-config-env-file \
--from-env-file=./game-env-file.properties
#指定键名
kubectl create configmap game-config-3 --from-file=<我的键名>=<文件路径>
#根据字面值创建ConfigMap
kubectl create configmap special-config --from-literal=special.how=very --from-literal=special.type=charm
使用生成器创建ConfigMap(生成器应在目录内的kustomization.yaml
中指定):
configMapGenerator:
- name: game-config-4
labels:
game-config: config-4
files:
- ./game.properties
#指定键名
configMapGenerator:
- name: game-config-5
labels:
game-config: config-5
files:
- game-special-key=./game.properties
#基于字面值生成
configMapGenerator:
- name: special-config-2
literals:
- special.how=very
- special.type=charm
kubectl apply -k .
使用正常的yaml创建:
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
special.how: very
---
apiVersion: v1
kind: ConfigMap
metadata:
name: env-config
namespace: default
data:
log_level: INFO
---
apiVersion: v1
kind: ConfigMap
metadata:
name: example-redis-config
data:
redis-config: |
maxmemory 2mb
maxmemory-policy allkeys-lru
将special-config中的special.how和log_level注入环境变量中:
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/sh", "-c", "env" ]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: special.how
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: env-config
key: log_level
restartPolicy: Never
将ConfigMap中的所有键值对都配置为容器环境变量:
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/sh", "-c", "env" ]
envFrom:
- configMapRef:
name: special-config
restartPolicy: Never
在Pod命令中使用ConfigMap定义的环境变量:
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/echo", "$(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ]
env:
- name: SPECIAL_LEVEL_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: SPECIAL_LEVEL
- name: SPECIAL_TYPE_KEY
valueFrom:
configMapKeyRef:
name: special-config
key: SPECIAL_TYPE
restartPolicy: Never
可以将ConfigMap中的键名成为volume的文件名,值成为volume的文件内容。
当已挂载的 ConfigMap 被更新时,所投射的内容最终也会被更新。 这适用于 Pod 启动后可选引用的 ConfigMap 重新出现的情况。(subPath不会更新)
例如下面的configmap:
apiVersion: v1
kind: ConfigMap
metadata:
name: special-config
namespace: default
data:
SPECIAL_LEVEL: very
SPECIAL_TYPE: charm
下面的Pod将在/etc/config
目录下面映射configmap的键值。
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/sh", "-c", "ls /etc/config/" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
# 提供包含要添加到容器中的文件的 ConfigMap 的名称
name: special-config
restartPolicy: Never
如果要使用其他的字符编码,需要用binaryData
使用path
字段可以指定映射到volume中的文件名。此时/etc/config
路径下面SPECIAL_LEVEL
生成的文件名将变成keys
apiVersion: v1
kind: Pod
metadata:
name: dapi-test-pod
spec:
containers:
- name: test-container
image: registry.k8s.io/busybox
command: [ "/bin/sh","-c","cat /etc/config/keys" ]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: special-config
items:
- key: SPECIAL_LEVEL
path: keys
restartPolicy: Never
下面是一个redis的yaml:
apiVersion: v1
kind: Pod
metadata:
name: redis
spec:
containers:
- name: redis
image: redis:5.0.4
command:
- redis-server
- "/redis-master/redis.conf"
env:
- name: MASTER
value: "true"
ports:
- containerPort: 6379
resources:
limits:
cpu: "0.1"
volumeMounts:
- mountPath: /redis-master-data
name: data
- mountPath: /redis-master
name: config
volumes:
- name: data
emptyDir: {}
- name: config
configMap:
name: example-redis-config
items:
- key: redis-config
path: redis.conf
其中redis的redis.conf来自configMap的redis-config
键。
简单给两个配置:
apiVersion: v1
kind: ConfigMap
metadata:
name: example-redis-config
data:
redis-config: |
maxmemory 2mb
maxmemory-policy allkeys-lru
分别apply之后:
127.0.0.1:6379> CONFIG GET maxmemory
1) "maxmemory"
2) "2097152"
127.0.0.1:6379> CONFIG GET maxmemory-policy
1) "maxmemory-policy"
2) "allkeys-lru"
说明配置成功。这个例子说明了现实的Redis等应用与ConfigMap的结合使用。
讨论计算资源的时候,主要是指 CPU 和 内存。
针对每个容器,你都可以指定其资源限制和请求,包括如下选项:
spec.containers[].resources.limits.cpu
spec.containers[].resources.limits.memory
spec.containers[].resources.limits.hugepages-<size>
spec.containers[].resources.requests.cpu
spec.containers[].resources.requests.memory
spec.containers[].resources.requests.hugepages-<size>
cpu的单位一般是m,100m代表0.1个cpu,内存的单位一般是Mi,1GiB=1024MiB。
真正使用的资源一般比requests多,比limits少。例子:
apiVersion: v1
kind: Pod
metadata:
name: frontend
spec:
containers:
- name: app
image: images.my-company.example/app:v4
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
- name: log-aggregator
image: images.my-company.example/log-aggregator:v6
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
当您创建 Pod 时(直接创建,或者通过控制器创建),Kubernetes 调度程序选择一个节点去运行该 Pod。每一个节点都有一个最大可提供的资源数量:CPU 数量和内存大小。调度程序将确保:对于每一种资源类型,已调度的 Pod 对该资源的请求之和小于该节点最大可供使用资源数量。
Kubernetes 一共提供了四种方法,可以将 Pod 调度到指定的节点上,这些方法从简便到复杂的顺序如下:
- 指定节点 nodeName
- 节点选择器 nodeSelector (Kubernetes 推荐用法)
- Affinity and anti-affinity(亲和性与反亲和性)
- Pod拓扑分布约束
指定nodeName字段来调度Pod。使用 nodeName
规则的优先级会高于使用 nodeSelector
或亲和性与非亲和性的规则。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeName: kube-01
通过为节点添加标签,让 Pod 调度到特定节点或节点组上。 建议选择节点上的kubelet无法修改的标签键。 这可以防止受感染的节点在自身上设置这些标签,进而影响调度器将工作负载调度到受感染的节点。
#列出节点的标签
kubectl get nodes --show-labels
#为节点设置标签
kubectl label nodes <your-node-name> disktype=ssd
通过nodeSelector调度到特定的节点上:
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
nodeSelector:
disktype: ssd
NodeRestriction
管理插件可以阻止 kubelet 设置或者修改节点上以 node-restriction.kubernetes.io/
开头的标签。如需要使用该标签前缀作为节点隔离的目的,需要:
- 确保 kubenetes 已经启用了 Node authorizer 和 NodeRestriction admission plugin
- 添加带
node-restriction.kubernetes.io/
前缀的标签到节点对象,并将这些标签作为 Pod 中的节点选择器。例如:example.com.node-restriction.kubernetes.io/fips=true
节点的亲和性:
节点的亲和性使用.spec.affinity.nodeAffinity
来描述,requiredDuringSchedulingIgnoredDuringExecution
意味着调度器只有在规则被满足的时候才能调度,preferredDuringSchedulingIgnoredDuringExecution
意味着调度器会尝试寻找满足对应规则的节点,如果找不到匹配的节点仍然会调度Pod。
下面的例子描述了Pod必须调度到存在键topology.kubernetes.io/zone,值为antarctica-east1或者antarctica-west1的标签的节点,且节点最好具有键为another-node-label-key且值为another-node-label-value的标签。
apiVersion: v1
kind: Pod
metadata:
name: with-node-affinity
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values:
- antarctica-east1
- antarctica-west1
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: another-node-label-key
operator: In
values:
- another-node-label-value
containers:
- name: with-node-affinity
image: registry.k8s.io/pause:2.0
例子中使用了操作符 In
。节点亲和性支持如下操作符:In
、NotIn
、Exists
、DoesNotExist
、Gt
、Lt
。
如果某个 Pod 同时指定了 nodeSelector
和 nodeAffinity
,则目标节点必须同时满足两个条件,才能将 Pod 调度到该节点上。
如果为 nodeAffinity
指定多个 nodeSelectorTerms
,则目标节点只需要满足任意一个 nodeSelectorTerms
的要求,就可以将 Pod 调度到该节点上。
如果为 nodeSelectorTerms
指定多个 matchExpressions
,则目标节点必须满足所有的 matchExpressions
的要求,才能将 Pod 调度到该节点上。
节点亲和性规则只在调度Pod时发现,调度之后将不影响Pod(如果Node标签变化的话)。
Pod亲和性和反亲和性:
Pod之间的亲和性与反亲和性(inter-pod affinity and anti-affinity)可以基于已经运行在节点上的 Pod 的标签(而不是节点的标签)来限定 Pod 可以被调度到哪个节点上。
如果 X 上已经运行了一个或多个满足规则 Y 的 Pod, 则这个 Pod 应该(或者在反亲和性的情况下不应该)运行在 X 上”。 这里的 X 可以是节点、机架、云提供商可用区或地理区域或类似的拓扑域, Y 则是 Kubernetes 尝试满足的规则。(一般用标签选择算符来表达Y,用topologyKey来表达X的概念,其取值是系统用来标示域的节点标签键。 )
要使用 Pod 间亲和性,可以使用 Pod 规约中的 .affinity.podAffinity
字段。 对于 Pod 间反亲和性,可以使用 Pod 规约中的 .affinity.podAntiAffinity
字段。
下面的例子功能是Pod可以被调度到包含key为failure-domain.beta.kubernetes.io/zone
的标签且必须有一个包含标签security=S1的Pod的节点,并且Pod最好不要调度到包含key为failure-domain.beta.kubernetes.io/zone
的标签且有一个标签security=S2的Pod的节点
apiVersion: v1
kind: Pod
metadata:
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: failure-domain.beta.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: failure-domain.beta.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: k8s.gcr.io/pause:2.0
关于topologyKey的限制:
- 对亲和性以及
requiredDuringSchedulingIgnoredDuringExecution
Pod 反亲和性,topologyKey
不能为空 - 对
requiredDuringSchedulingIgnoredDuringExecution
Pod 反亲和性,管理控制器LimitPodHardAntiAffinityTopology
被用来限制topologyKey
必须为kubernetes.io/hostname
。如果想要使用其他的自定义 topology,必须修改该管理控制器,或者将其禁用 - 对
preferredDuringSchedulingIgnoredDuringExecution
Pod 反亲和性,如果topologyKey
为空,则代表所有的 topology (此时,不局限于kubernetes.io/hostname
、failure-domain.beta.kubernetes.io/zone
和failure-domain.beta.kubernetes.io/region
的组合) - 除了上述的情形以外,
topologyKey
可以是任何合法的标签 Key
下面是一个例子用来让三个节点分别运行一个redis和web应用程序:
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-cache
spec:
selector:
matchLabels:
app: store
replicas: 3
template:
metadata:
labels:
app: store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis-server
image: redis:3.2-alpine
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-server
spec:
selector:
matchLabels:
app: web-store
replicas: 3
template:
metadata:
labels:
app: web-store
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web-store
topologyKey: "kubernetes.io/hostname"
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- store
topologyKey: "kubernetes.io/hostname"
containers:
- name: web-app
image: nginx:1.12-alpine
污点和容忍(taints and tolerations)成对工作,以确保 Pod 不会被调度到不合适的节点上。
- 可以为节点增加污点(taints,一个节点可以有 0-N 个污点)
- 可以为 Pod 增加容忍(toleration,一个 Pod 可以有 0-N 个容忍)
- 如果节点上存在污点,则该节点不会接受任何不能容忍(tolerate)该污点的 Pod
#向节点添加污点,键名=键值:效果
kubectl taint nodes node1 key=value:NoSchedule
#删除节点上的污点
kubectl taint nodes node1 key:NoSchedule-
下面是使用了容忍度的Pod,容忍度匹配对应的污点,如果是Exists则key和effect一样,如果是Equal,则还需要value一样。
apiVersion: v1
kind: Pod
metadata:
name: nginx
labels:
env: test
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
tolerations:
- key: "example-key"
operator: "Exists"
effect: "NoSchedule"
-----
tolerations:
- key: "example-key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
如果一个容忍度的 key
为空且 operator
为 Exists
, 表示这个容忍度与任意的 key、value 和 effect 都匹配,即这个容忍度能容忍任何污点。
如果 effect
为空,则可以与所有键名 key1
的效果相匹配。
污点中的effect:
NoSchedule
,有匹配的容忍度才可以调度,当前节点上的Pod不会被驱除。PreferNoSchedule
比NoSchedule
更宽容一些,Kubernetes 将尽量避免将没有匹配容忍的 Pod 调度到该节点上,但是并不是不可以NoExecute
不能在节点上运行。如果已经运行,不能容忍污点的Pod将被驱逐,能容忍污点的Pod上如果定义了tolerationSeconds,则tolerationSeconds时间后会被驱除。如果没定义tolerationSeconds,则会一致在节点上。
当某种条件为真时,节点控制器会自动给节点添加一个污点。当前内置的污点包括:
node.kubernetes.io/not-ready
:节点未准备好。这相当于节点状况Ready
的值为 "False
"。node.kubernetes.io/unreachable
:节点控制器访问不到节点. 这相当于节点状况Ready
的值为 "Unknown
"。node.kubernetes.io/memory-pressure
:节点存在内存压力。node.kubernetes.io/disk-pressure
:节点存在磁盘压力。node.kubernetes.io/pid-pressure
:节点的 PID 压力。node.kubernetes.io/network-unavailable
:节点网络不可用。node.kubernetes.io/unschedulable
:节点不可调度。node.cloudprovider.kubernetes.io/uninitialized
:如果 kubelet 启动时指定了一个“外部”云平台驱动, 它将给当前节点添加一个污点将其标志为不可用。在 cloud-controller-manager 的一个控制器初始化这个节点后,kubelet 将删除这个污点。
因此当节点出现问题时,Pod被驱逐实际上是因为节点被添加了对应的污点,Pod不能容忍从而被驱逐。
(不是很确定,剩下的几种条件似乎属于8.4.3的内容,并不是NoExecute?)
kubelet 会添加带有 NoExecute
效果的相关污点。 此效果被默认添加到 node.kubernetes.io/not-ready
和 node.kubernetes.io/unreachable
污点中。
你可能希望在出现网络分裂事件时,对于一个与节点本地状态有着深度绑定的应用而言, 仍然停留在当前节点上运行一段较长的时间,以等待网络恢复以避免被驱逐。 你为这种 Pod 所设置的容忍度看起来可能是这样:
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 6000
这样这个Pod当遇到节点不可达的时候,会停留在节点上100min以等待节点恢复。
Kubernetes 会自动给 Pod 添加针对 node.kubernetes.io/not-ready
和 node.kubernetes.io/unreachable
的容忍度,且配置 tolerationSeconds=300
DaemonSet的Pod针对以下污点自动添加的 NoExecute
的容忍度且不指定 tolerationSeconds
,因此永远不会被驱逐:
node.kubernetes.io/unreachable
node.kubernetes.io/not-ready
控制平面使用节点控制器自动创建 与节点状况对应的、效果为 NoSchedule
的污点。这样的话,调度器在进行调度时检查污点,而不是检查节点状况。这确保节点状况不会直接影响调度。
例如一个节点的状况是DiskPressure,就会添加node.kubernetes.io/disk-pressure
污点。
DaemonSet 控制器自动为所有守护进程添加如下 NoSchedule
容忍度,以防 DaemonSet 崩溃:
node.kubernetes.io/memory-pressure
node.kubernetes.io/disk-pressure
node.kubernetes.io/pid-pressure
(1.14 或更高版本)node.kubernetes.io/unschedulable
(1.10 或更高版本)node.kubernetes.io/network-unavailable
(只适合主机网络配置)
(感觉理解官方文档这部分的内容都写的很奇怪,并没有写清哪部分是NoSchedule哪部分是NoExecute)
Kubernetes Secret
对象可以用来储存敏感信息,例如:密码、OAuth token、ssh 密钥等。如果不使用 Secret
,此类信息可能被放置在 Pod 定义中或者容器镜像中。将此类敏感信息存储到 Secret
中,可以更好地:
- 控制其使用
- 降低信息泄露的风险
使用kubectl创建Secret:
kubectl create secret generic db-user-pass \
--from-literal=username=admin \
--from-literal=password='S!B\*d$zDsb='
kubectl create secret generic db-user-pass \
--from-file=./username.txt \
--from-file=./password.txt
#设置键名
kubectl create secret generic db-user-pass \
--from-file=username=./username.txt \
--from-file=password=./password.txt
#解码secret
kubectl get secret db-user-pass -o jsonpath='{.data.password}' | base64 -d
#编辑secret
kubectl edit secret db-user-pass
#删除secret
kubectl delete secret db-user-pass
使用配置文件创建Secret:
data
字段用来存储 base64 编码的任意数据。 提供 stringData
字段是为了方便,它允许 Secret 使用未编码的字符串。 data
和 stringData
的键必须由字母、数字、-
、_
或 .
组成。
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
---
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
stringData:
config.yaml: |
apiUrl: "https://my.api.com/api/v1"
username: <user>
password: <password>
Secret 的 stringData
字段不能很好地与服务器端应用配合使用。
创建通过卷访问Secret的Pod:
apiVersion: v1
kind: Pod
metadata:
name: secret-test-pod
spec:
containers:
- name: test-container
image: nginx
volumeMounts:
# name 必须与下面的卷名匹配
- name: secret-volume
mountPath: /etc/secret-volume
readOnly: true
# Secret 数据通过一个卷暴露给该 Pod 中的容器
volumes:
- name: secret-volume
secret:
secretName: test-secret
可以使用.spec.volumes[].secret.items
来改变每个键的目标路径:
volumes:
- name: foo
secret:
secretName: mysecret
items:
- key: username
path: my-group/my-username
这时候username文件将被挂载到/etc/secret-volume/my-group/my-username
。但是需要注意:
- 只有在
items
字段中指定的键才会被映射。 - 要使用 Secret 中全部的键,那么全部的键都必须列在
items
字段中。 - 所有列出的键必须存在于相应的 Secret 中。否则,该卷不被创建。
指定defaultMode可以设置文件的权限:
volumes:
- name: foo
secret:
secretName: mysecret
defaultMode: 0400
使用Secret中的数据定义容器的变量:
apiVersion: v1
kind: Pod
metadata:
name: envvars-multiple-secrets
spec:
containers:
- name: envars-test-container
image: nginx
env:
- name: BACKEND_USERNAME
valueFrom:
secretKeyRef:
name: backend-user
key: backend-username
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-user
key: db-username
将secret的所有键定义为容器的环境变量:
apiVersion: v1
kind: Pod
metadata:
name: envfrom-secret
spec:
containers:
- name: envars-test-container
image: nginx
envFrom:
- secretRef:
name: test-secret
可以将特定的Secret和ConfigMap标记为不可更改(Immutable):
- 防止意外(或非预期的)更新导致应用程序中断
- 将 Secret 标记为不可变,可以极大降低 kube-apiserver 的负载,提升集群性能。 kubelet 不需要监视那些被标记为不可更改的 Secret。
只需要将immutable
字段设置为true即可:
apiVersion: v1
kind: Secret
metadata:
...
data:
...
immutable: true
内置类型 | 用法 |
---|---|
Opaque |
用户定义的任意数据 |
kubernetes.io/service-account-token |
服务账号令牌 |
kubernetes.io/dockercfg |
~/.dockercfg 文件的序列化形式 |
kubernetes.io/dockerconfigjson |
~/.docker/config.json 文件的序列化形式 |
kubernetes.io/basic-auth |
用于基本身份认证的凭据 |
kubernetes.io/ssh-auth |
用于 SSH 身份认证的凭据 |
kubernetes.io/tls |
用于 TLS 客户端或者服务器端的数据 |
bootstrap.kubernetes.io/token |
启动引导令牌数据 |
type被为空则被视为Opaque
类型。如果要使用内置类型之一, 则必须满足为该类型所定义的所有要求。
对于集群管理员来说:
- 配置静态加密。Secret对象以非加密的形式存储在etcd中,可以配置对在etcd中存储的Secret数据加密。
- 配置Secret资源的最少特权访问。
- 改进etcd管理策略。不再使用
etcd
所使用的持久存储时,考虑擦除或粉碎这些数据。如果存在多个etcd
实例,则在实例之间配置加密的 SSL/TLS 通信以保护传输中的 Secret 数据。 - 配置对外部Secret的访问权限。
对于开发者来说:
- 限制特定容器集合才能访问Secret。
- 读取后保护Secret数据。你的应用程序必须避免以明文记录 Secret 数据,还必须避免将这些数据传输给不受信任的一方。
- 避免共享Secret清单。
只简单练习一下Secret的静态存储加密,更多内容参考https://kubernetes.io/zh-cn/docs/tasks/administer-cluster/encrypt-data/
创建/etc/kubernetes/enc/enc.yaml
:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
# 参见以下文本了解有关 Secret 值的详情
secret: QTL+ulYpba7vezXqd7zeR3bgexyOui8H+34YaeZXpwU=
- identity: {} # 这个回退允许读取未加密的 Secret;
# 例如,在初始迁移期间
逻辑是以providers下的第一个提供程序进行加密,解密的时候从上往下依次尝试解密。因此这个文件的作用就是以aescbc进行加密,提供了identity以便于之前未加密的secret也可以正常读取。
修改api-server,即修改master节点上的/etc/kubernetes/manifests/kube-apiserver.yaml
,因为是静态Pod:
---
#
# 这是一个静态 Pod 的清单片段。
# 检查是否适用于你的集群和 API 服务器。
#
apiVersion: v1
kind: Pod
metadata:
annotations:
kubeadm.kubernetes.io/kube-apiserver.advertise-address.endpoint: 10.20.30.40:443
creationTimestamp: null
labels:
app.kubernetes.io/component: kube-apiserver
tier: control-plane
name: kube-apiserver
namespace: kube-system
spec:
containers:
- command:
- kube-apiserver
...
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml # 增加这一行
volumeMounts:
...
- name: enc # 增加这一行
mountPath: /etc/kubernetes/enc # 增加这一行
readOnly: true # 增加这一行
...
volumes:
...
- name: enc # 增加这一行
hostPath: # 增加这一行
path: /etc/kubernetes/enc # 增加这一行
type: DirectoryOrCreate # 增加这一行
...
这样会重启api-server,如果没重启就delete掉。
这样的结果就是当前secret内的不会被加密,之后创建的secret都会被加密:
etcdctl get /registry/secrets/default/mysecret|hexdump -C
00000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret|
00000010 73 2f 64 65 66 61 75 6c 74 2f 6d 79 73 65 63 72 |s/default/mysecr|
00000020 65 74 0a 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 |et.k8s:enc:aescb|
00000030 63 3a 76 31 3a 6b 65 79 31 3a 70 af a1 64 e8 47 |c:v1:key1:p��d�G|
可以正常解密:
kubectl get secret mysecret -o yaml
apiVersion: v1
data:
password: MWYyZDFlMmU2N2Rm
username: YWRtaW4=
kind: Secret
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"v1","data":{"password":"MWYyZDFlMmU2N2Rm","username":"YWRtaW4="},"kind":"Secret","metadata":{"annotations":{},"name":"mysecret","namespace":"default"},"type":"Opaque"}
creationTimestamp: "2023-11-01T11:29:05Z"
name: mysecret
namespace: default
resourceVersion: "1069097"
uid: 48da1d97-e861-4299-a35a-efd8007a03cc
type: Opaque
如果想把之前的secret也都加密,运行下面的命令:
# 以能够读写所有 Secret 的管理员身份运行此命令
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
如果要解密所有数据,设置这样的enc.yaml:
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- identity: {}
- aescbc:
keys:
- name: key1
secret: <BASE 64 ENCODED SECRET>
然后再次运行:
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
这样就可以解密出所有的secrets然后replace,replace的时候用的是identity,解除了静态加密。
可以配置加密提供程序配置的自动重新加载。默认是API 服务器仅在启动时加载一次为 --encryption-provider-config
指定的文件。 要允许自动重新加载, 可使用 --encryption-provider-config-automatic-reload=true
。启用此选项可允许你在不重启 API 服务器的情况下更改静态加密所需的密钥。
添加这个选项重启api-server后,修改enc.yaml中的identity注释掉,发现就不能正常读取之前的secret了,说明实现了自动加载:
kubectl get secret
Error from server (InternalError): Internal error occurred: unable to transform key "/registry/secrets/default/goatvault": no matching prefix found
安全上下文(Security Context)定义 Pod 或 Container 的特权与访问控制设置。 安全上下文包括但不限于:
-
自主访问控制(Discretionary Access Control): 基于用户 ID(UID)和组 ID(GID) 来判定对对象(例如文件)的访问权限。
-
安全性增强的 Linux(SELinux): 为对象赋予安全性标签。
-
以特权模式或者非特权模式运行。
-
Linux 权能: 为进程赋予 root 用户的部分特权而非全部特权。
-
AppArmor:使用程序配置来限制个别程序的权能。
-
Seccomp:过滤进程的系统调用。
-
allowPrivilegeEscalation
:控制进程是否可以获得超出其父进程的特权。 此布尔值直接控制是否为容器进程设置no_new_privs
标志。 当容器满足一下条件之一时,allowPrivilegeEscalation
总是为 true:- 以特权模式运行,或者
- 具有
CAP_SYS_ADMIN
权能
-
readOnlyRootFilesystem
:以只读方式加载容器的根文件系统。
为Pod和Container设置安全上下文都是设置securityContext字段。
runAsUser、runAsGroup、runAsNonRoot、、runAsUserName、fsGroup等
例如如下Pod:
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo
spec:
securityContext:
runAsUser: 1000
runAsGroup: 3000
fsGroup: 2000
volumes:
- name: sec-ctx-vol
emptyDir: {}
containers:
- name: sec-ctx-demo
image: busybox:1.28
command: [ "sh", "-c", "sleep 1h" ]
volumeMounts:
- name: sec-ctx-vol
mountPath: /data/demo
securityContext:
allowPrivilegeEscalation: false
设置后,runAsUser指定Pod中所有容器内的进程都以用户ID 1000来运行,runAsGroup指定所有容器内的进程都以主组ID 2000啦运行,且所有创建的文件也会划归用户 1000 和组 3000。fsGroup指定容器中挂载的卷以及在卷下面创建的所有文件都的属组ID都是2000:
/tmp $ ps aux
PID USER TIME COMMAND
1 1000 0:00 sh -c sleep 1h
10 1000 0:00 /bin/sh
20 1000 0:00 ps aux
/tmp $ ls -al /data/
total 12
drwxr-xr-x 3 root root 4096 Nov 1 12:29 .
drwxr-xr-x 1 root root 4096 Nov 1 12:29 ..
drwxrwsrwx 2 root 2000 4096 Nov 1 12:28 demo
/tmp $ id
uid=1000 gid=3000 groups=2000,3000
为容器设置同理:
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo-2
spec:
securityContext:
runAsUser: 1000
containers:
- name: sec-ctx-demo-2
image: gcr.io/google-samples/node-hello:1.0
securityContext:
runAsUser: 2000
allowPrivilegeEscalation: false
且当容器的securityContext和Pod的起冲突的时候,容器的securityContext会覆盖Pod的冲突的设置。因此uid是2000。
为容器设置SELinux标签,例子如下:
...
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
要指定 SELinux,需要在宿主操作系统中装载 SELinux 安全性模块。
设置privileged为true即可赋予容器特权模式。
securityContext:
privileged: true
使用Linux权能可以赋予进程root用户所拥有的某些特权。
apiVersion: v1
kind: Pod
metadata:
name: security-context-demo-4
spec:
containers:
- name: sec-ctx-4
image: gcr.io/google-samples/node-hello:1.0
securityContext:
capabilities:
add: ["NET_ADMIN", "SYS_TIME","SYS_ADMIN"]
Linux 权能常数定义的形式为 CAP_XXX
。但是你在 container 清单中列举权能时, 要将权能名称中的 CAP_
部分去掉。例如,要添加 CAP_SYS_TIME
, 可在权能列表中添加 SYS_TIME
。
参考https://kubernetes.io/zh-cn/docs/tutorials/security/apparmor/,感觉很少见,以后遇到时再看
不知道是个什么东西感觉很少见,以后遇到了再了解:
...
securityContext:
seccompProfile:
type: RuntimeDefault
...
securityContext:
seccompProfile:
type: Localhost
localhostProfile: my-profiles/profile-allow.json
Pod可以有优先级。 优先级表示一个 Pod 相对于其他 Pod 的重要性。 如果一个 Pod 无法被调度,调度程序会尝试抢占(驱逐)较低优先级的 Pod, 以使悬决 Pod 可以被调度。
- 新增一个或多个 PriorityClass。
- 创建 Pod,并将其
priorityClassName
设置为新增的 PriorityClass。 通常,你将会添加priorityClassName
到集合对象(如 Deployment) 的 Pod 模板中
PriorityClass:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
preemptionPolicy: Never
description: "此优先级类应仅用于 XYZ 服务 Pod。"
value即优先级,preemptionPolicy指的是是否支持抢占,默认是PreemptLowerPriority
,即支持抢占。则这将允许该 PriorityClass 的 Pod 抢占较低优先级的 Pod。 如果 preemptionPolicy
设置为 Never
,则该 PriorityClass 中的 Pod 将是非抢占式的。
当启用 Pod 优先级时,调度程序会按优先级对悬决 Pod 进行排序, 并且每个悬决的 Pod 会被放置在调度队列中其他优先级较低的悬决 Pod 之前。 因此,如果满足调度要求,较高优先级的 Pod 可能会比具有较低优先级的 Pod 更早调度。 如果无法调度此类 Pod,调度程序将继续并尝试调度其他较低优先级的 Pod。
https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/authentication/
TODO
基于角色(Role)的访问控制(RBAC)是一种基于组织中用户的角色来调节控制对计算机或网络资源的访问的方法。
开启:
- --authorization-mode=Node,RBAC
RBAC API声明了四种Kubernetes对象:Role、ClusterRole、RoleBinding 和 ClusterRoleBinding。
Role是在某个namepsace内设置访问权限,ClusterRole是在集群内设置访问权限。
角色绑定(Role Binding)是将角色中定义的权限赋予一个或者一组用户。 它包含若干主体(Subject)(用户、组或服务账户)的列表和对这些主体所获得的角色的引用。 RoleBinding 在指定的名字空间中执行授权,而 ClusterRoleBinding 在集群范围执行授权。
Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
ClusterRole:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
# "namespace" 被忽略,因为 ClusterRoles 不受名字空间限制
name: secret-reader
rules:
- apiGroups: [""]
# 在 HTTP 层面,用来访问 Secret 资源的名称为 "secrets"
resources: ["secrets"]
verbs: ["get", "watch", "list"]
RoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pod
# 你需要在该名字空间中有一个名为 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
name: jane # "name" 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定使得用户 "dave" 能够读取 "development" 名字空间中的 Secret
# 你需要一个名为 "secret-reader" 的 ClusterRole
kind: RoleBinding
metadata:
name: read-secrets
# RoleBinding 的名字空间决定了访问权限的授予范围。
# 这里隐含授权仅在 "development" 名字空间内的访问权限。
namespace: development
subjects:
- kind: User
name: dave # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
ClusterRoleBinding:
apiVersion: rbac.authorization.k8s.io/v1
# 此集群角色绑定允许 “manager” 组中的任何人访问任何名字空间中的 Secret 资源
kind: ClusterRoleBinding
metadata:
name: read-secrets-global
subjects:
- kind: Group
name: manager # 'name' 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
使用命令行:
#role
kubectl create role pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
#指定apiGroups
kubectl create clusterrole foo --verb=get,list,watch --resource=replicasets.apps
#clusterrole
kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
#rolebinding
kubectl create rolebinding bob-admin-binding --clusterrole=admin --user=bob --namespace=acme
kubectl create rolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp --namespace=acme
kubectl create rolebinding myappnamespace-myapp-view-binding --clusterrole=view --serviceaccount=myappnamespace:myapp --namespace=acme
#clusterrolebinding
kubectl create clusterrolebinding root-cluster-admin-binding --clusterrole=cluster-admin --user=root
kubectl create clusterrolebinding kube-proxy-binding --clusterrole=system:node-proxier --user=system:kube-proxy
kubectl create clusterrolebinding myapp-view-binding --clusterrole=view --serviceaccount=acme:myapp
RBAC API 会阻止用户通过编辑角色或者角色绑定来提升权限。 由于这一点是在 API 级别实现的,所以在 RBAC 鉴权组件未启用的状态下依然可以正常工作。
只有在符合下列条件之一的情况下,你才能创建/更新角色:
- 你已经拥有角色中包含的所有权限,且其作用域与正被修改的对象作用域相同。 (对 ClusterRole 而言意味着集群范围,对 Role 而言意味着相同名字空间或者集群范围)。
- 你被显式授权在
rbac.authorization.k8s.io
API 组中的roles
或clusterroles
资源使用escalate
动词。
只有你已经具有了所引用的角色中包含的全部权限时,或者你被授权在所引用的角色上执行 bind
动词时,你才可以创建或更新角色绑定。这里的权限与角色绑定的作用域相同。
例子:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: role-grantor
rules:
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings"]
verbs: ["create"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
verbs: ["bind"]
# 忽略 resourceNames 意味着允许绑定任何 ClusterRole
resourceNames: ["admin","edit","view"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: role-grantor-binding
namespace: user-1-namespace
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: role-grantor
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: user-1
例如创建如下的role并绑定:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""] # "" 标明 core API 组
resources: ["pods"]
verbs: ["get", "watch", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
# 此角色绑定允许 "jane" 读取 "default" 名字空间中的 Pod
# 你需要在该名字空间中有一个名为 “pod-reader” 的 Role
kind: RoleBinding
metadata:
name: read-pods
namespace: default
subjects:
# 你可以指定不止一个“subject(主体)”
- kind: User
name: feng # "name" 是区分大小写的
apiGroup: rbac.authorization.k8s.io
roleRef:
# "roleRef" 指定与某 Role 或 ClusterRole 的绑定关系
kind: Role # 此字段必须是 Role 或 ClusterRole
name: pod-reader # 此字段必须与你要绑定的 Role 或 ClusterRole 的名称匹配
apiGroup: rbac.authorization.k8s.io
然后:
#创建私钥
openssl genrsa -out feng.key 2048
#创建csr证书签名请求文件
openssl req -new -key feng.key -out feng.csr -subj "/CN=feng/O=MGM"
#为用户办法证书,其中-CA和-CAkey使用的是集群/etc/kubernetes/pki/下面的文件
openssl x509 -req -in feng.csr -CA pki/ca.crt -CAkey pki/ca.key -CAcreateserial -out feng.crt -days 365
#将用户feng的信息添加进kubectl
kubectl config set-credentials feng --client-certificate=feng.crt --client-key=feng.key
#为用户feng配置一个context
kubectl config set-context feng-k8s --cluster=kind-k8s --namespace=default --user=feng
#切换context
kubectl config use-context feng-k8s
#这时候的用户就是feng了。
kubectl get pods
kubectl get secret
创建下面的集群,把node的端口暴露出来。
(事实证明是不需要的!!!)
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
name: k8s-practise #集群名
nodes: #节点配置,如下,启动一个master节点,一个worker节点
- role: control-plane #master节点
image: kindest/node:v1.28.0 #指定镜像,同 kind create cluster参数--image
extraPortMappings:
- containerPort: 6443
hostPort: 36443
listenAddress: "0.0.0.0"
- containerPort: 2379
hostPort: 32380
listenAddress: "0.0.0.0"
- role: worker
image: kindest/node:v1.28.0
extraPortMappings:
- containerPort: 80
hostPort: 32080
listenAddress: "0.0.0.0"
- containerPort: 443
hostPort: 32443
listenAddress: "0.0.0.0"
- containerPort: 3306
hostPort: 33306
listenAddress: "0.0.0.0"
- containerPort: 6379
hostPort: 36379
listenAddress: "0.0.0.0"
- containerPort: 8080
hostPort: 38080
listenAddress: "0.0.0.0"
- role: worker
image: kindest/node:v1.28.0
extraPortMappings:
- containerPort: 80
hostPort: 32081
listenAddress: "0.0.0.0"
- containerPort: 443
hostPort: 32444
listenAddress: "0.0.0.0"
- containerPort: 3306
hostPort: 33307
listenAddress: "0.0.0.0"
- containerPort: 6379
hostPort: 36380
listenAddress: "0.0.0.0"
- containerPort: 8080
hostPort: 38081
listenAddress: "0.0.0.0"
- role: worker
image: kindest/node:v1.28.0
extraPortMappings:
- containerPort: 80
hostPort: 32082
listenAddress: "0.0.0.0"
- containerPort: 443
hostPort: 32445
listenAddress: "0.0.0.0"
- containerPort: 3306
hostPort: 33308
listenAddress: "0.0.0.0"
- containerPort: 6379
hostPort: 36381
listenAddress: "0.0.0.0"
- containerPort: 8080
hostPort: 38082
listenAddress: "0.0.0.0"
官方的教程里是一个主节点redis负责写数据,另外两个从节点redis负责度数据以实现读数据,但实际上并没有给这样的功能,单纯的起了3个redis,因此不如只起一个redis。至于redis集群的部署没有去看,不是很熟redis集群的搭建,以后再学。
redis主节点:
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis-leader
labels:
app: redis
role: leader
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
role: leader
tier: backend
spec:
containers:
- name: leader
image: redis:7.2.2
# resources:
# requests:
# cpu: 100m
# memory: 100Mi
ports:
- containerPort: 6379
---
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: v1
kind: Service
metadata:
name: redis-leader
labels:
app: redis
role: leader
tier: backend
spec:
ports:
- port: 6379
targetPort: 6379
selector:
app: redis
role: leader
tier: backend
php的前端:
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 3
selector:
matchLabels:
app: guestbook
tier: frontend
template:
metadata:
labels:
app: guestbook
tier: frontend
spec:
containers:
- name: php-redis
image: loveone/gb-frontend:v5
env:
- name: GET_HOSTS_FROM
value: "dns"
# resources:
# requests:
# cpu: 100m
# memory: 100Mi
ports:
- containerPort: 80
---
# 来源:https://cloud.google.com/kubernetes-engine/docs/tutorials/guestbook
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# 如果你的集群支持,请取消注释以下内容以自动为前端服务创建一个外部负载均衡 IP。
# type: LoadBalancer
#type: LoadBalancer
ports:
# 此服务应使用的端口
- port: 80
selector:
app: guestbook
tier: frontend
用的都是默认的ClusterIP模式,因为拿port-forward
来访问。真实的云环境就会用LoadBalancer负载均衡了。
kubectl port-forward svc/frontend 38080:80
再访问127.0.0.1:38080就可以成功访问到搭建好的php留言板。
可能有个究极坑点:DOCKER_DEFAULT_PLATFORM
,必须设置成arm64才可以,用完kind之后再改成amd64。
由于 kind 是通过 docker 容器模拟 node 来部署集群的,因此和普通集群有一些差异。主要包括以下几个方面:
- 文件系统
- kind cluster 中无法直接访问宿主机上的文件
- kind cluster 中无法直接使用宿主机上的镜像
- 网络
- 比如无法在宿主机直接访问 kind cluster 中的服务
# 查看 node 里的镜像列表
docker exec -it 0aa056d78210 crictl images
# 导入镜像到指定集群
kind load docker-image caas4/etcd:3.5.5 --name aio
kubectl cluster-info --context kind-k8s
#port-forward的作用是转发本地的端口到一个目标的端口,可以是pod、deployment、service、replicaset等。
kubectl port-forward mongo-75f59d57f4-4nd6q 28015:27017
kubectl port-forward pods/mongo-75f59d57f4-4nd6q 28015:27017
kubectl port-forward deployment/mongo 28015:27017
kubectl port-forward replicaset/mongo-75f59d57f4 28015:27017
kubectl port-forward service/mongo 28015:27017
# kubectl get 资源类型
#获取类型为Deployment的资源列表
kubectl get deployments
#获取类型为Pod的资源列表
kubectl get pods
#获取类型为Node的资源列表
kubectl get nodes
kubectl get services
# 查看所有名称空间的 Deployment
kubectl get deployments -A
kubectl get deployments --all-namespaces
# 查看 kube-system 名称空间的 Deployment
kubectl get deployments -n kube-system
#并非所有对象都在名称空间中
# 在名称空间里
kubectl api-resources --namespaced=true
# 不在名称空间里
kubectl api-resources --namespaced=false
# kubectl describe 资源类型 资源名称
#查看名称为nginx-XXXXXX的Pod的信息
kubectl describe pod nginx-XXXXXX
#查看名称为nginx的Deployment的信息
kubectl describe deployment nginx
# kubectl logs Pod名称
#查看名称为nginx-pod-XXXXXXX的Pod内的容器打印的日志
#本案例中的 nginx-pod 没有输出日志,所以您看到的结果是空的
kubectl logs -f nginx-pod-XXXXXXX
# kubectl exec Pod名称 -- 操作命令
# 在名称为nginx-pod-xxxxxx的Pod中运行bash
kubectl exec -it nginx-pod-xxxxxx -- /bin/bash
kubectl get services -o wide
kubectl get svc
#以yaml形式展现详细信息
kubectl get pods -o yaml
#多集群的情况
#所有集群的信息都在/Users/feng/.kube/config文件中
#通过下面的命令来切换context,实现集群的切换。
kubectl config use-context kind-k8s
#使用内置命令将 pod 端口从 Kubernetes 转发到本地
kubectl port-forward <PODNAME> 1234:80
#获取所有可用的API资源
kubectl api-resources
#冒充用户并获得使用权限进行验证和验证kubectl
kubectl auth can-i create pods
kubectl auth can-i --list
#获取Kubernetes 集群范围的所有资源
kubectl get all -n kube-system
kubectl get secrets -n kube-system
#查找对应的pod -l
kubectl get pods --namespace default -l "job-name=batch-check-job"
#上述操作默认都是在default namespaces中
kubectl get namespaces
kubectl get pods -n secure-middleware
#在别的命令空间操作都要带上-n
#查看kubectl上下文
kubectl config view
#查看当前的上下文
kubectl config current-context
#创建上下文以便在不同namespace中工作,设置上下文的namespace,cluster和name(cluster和name从kubectl config view中获得)
kubectl config set-context dev --namespace=development --cluster=kind-k8s --user=kind-k8s
kube-apiserver: 6443, 8080
kubectl proxy: 8080, 8081
kubelet: 10250, 10255, 4149
dashboard: 30000
docker api: 2375
etcd: 2379, 2380
kube-controller-manager: 10252
kube-proxy: 10256, 31442
kube-scheduler: 10251
weave: 6781, 6782, 6783
kubeflow-dashboard: 8080
https://tttang.com/archive/1465/
https://tttang.com/archive/1389/