K8S架构概述

k8s技术分享

背景

容器:隔离不同服务,避免依赖冲突;为服务提供一致性的环境;(docker, rkt......)

微服务化:大型单体应用被拆分成很多小的、可独立部署的组件,以便於单组件快速迭代升级、降低单模块的物理资源;

带来的问题:部署、运维的人力成本大幅提升(组件间依赖关系、扩缩容、故障检测和处理、负载均衡、自恢复);

解决方案:自动编排技术(Google k8s、Docker Swarm、Apache Mesos)

简单来说,就是docker规模增长带来了如何自动化编排的问题,k8s就是答案之一。

什么是k8s

关键词:2014  google Borg Omega 部署平台 go

k8s有什么优势

屏蔽底层细节:通过基础设施抽象,提供一个统一的部署平台给开发者,开发者无需关心底层硬件、操作系统细节;

高利用率:自动调度,提高资源利用率;

丰富的运维功能:提供服务发现、扩缩容、负载均衡、故障检测和处理、选主等功能;

市场份额:77%(2019)

google搜索热度

 

k8s概念和架构

资源概述

k8s中的操作对象都被看作是资源,如节点、pod、Deployment、LimitRequest,可以类比java中的类

资源名称

概念

node 工作节点
pod(po)

pod是对容器的封装,是k8s管理的最小单元,一个pod里可以有多个应用容器,也可以有多个init containers;

apiVersion: v1

kind: Pod

metadata:

  name: nginx                   # pod名

  labels:

    name: nginx                 # pod标签

spec:

  containers:

  - name: nginx

    image: nginx                # pod容器使用的镜像

    tag: latest

    ports:

    - containerPort: 80         # 容器端口

      hostPort: 80              # 宿主机映射端口

service(svc)

service是对pod 封装,对pod的访问可以通过service,实现负载均衡,避免pod扩缩容、重新部署带来的ip、端口变更;

apiVersion: v1

kind: Service

metadata:

  name: nginx-svc               # svc name

spec:

  type: NodePort                # svc port类型,NodePort会在集群每个node上都开一个固定端口,转发到service;还有一种是ClusterIp

  ports:

   - port: 80                   # svc port     

     nodePort: 30082            # 集群端口

  selector:

    app: nginx                  # 此svc后端是所有带有app=nginx这个标签的pod

endpoint service和pod之间不是直连,service通过selector选择器,筛选pod构造出包含pod 的ip、端口的endpoint资源。可以创建一个没有selector的service,手动构造endpoint。
ReplicationCotroller(rc) k8s用于管理集群内pod的资源,通过在rc内指定selector来关联pod。rc内需要指定pod 副本数量。如果pod修改了标签,会成为孤儿节点,脱离rc管控,rc也会立刻新建一个pod。
ReplicaSet(rs) rc的升级版,支持更高级语法的selector,例如多标签选择、In、NotIn等语法。
Deployment

封装了rs,支持滚动升级

  • 不需要在metadata.name中指定版本号,而是根据pod描述自动计算hash值;
  • 可以指定升级策略:Recreate or RollingUpdate
  • 可以降低升级速度:指定minReadySeconds,通常跟就绪探针一起使用,即某个pod在指定时间内,如果都是就绪状态,才会继续升级,否则会终止升级
  • 支持升级中暂停/继续:
    • kubectl rollout pause deployment ${deployment-name}
    • kubectl rollout resume deployment ${deployment-name}
  • 可以设置滚动升级速率:指定maxSurge和maxUnavailable两个参数,都支持百分比或绝对值
    • maxSurge:允许超出副本数量的上限,用于控制新pod创建;
    • maxUnavailable:最大不可用副本的数量,用于控制旧pod删除;
  • 支持回滚:kubectl rollout undo deployment ${deployment-name} --to-revision=${vision}

 

apiVersion: apps/v1beta2

kind: Deployment

metadata:

  name: nginx

spec:

  replicas: 1                                               # 副本数量

  selector:

    matchLabels:

      app: nginx                                      # 管理的pod的标签

  template:                                                

    metadata:

      labels:

        app: nginx

    spec:

      affinity:

        nodeAffinity:                                       # 节点亲缘性

          requiredDuringSchedulingIgnoredDuringExecution:   # 只针对新部署的pod,不针对已在运行的pod

            nodeSelectorTerms:

            - matchExpressions:

              - key: kubernetes.io/cluster-role             # node上可以打污点,跟此处对应上才可调度

                operator: NotIn

                values:

                - slave

      hostNetwork: true                                     # 使用主机网络模式

      dnsPolicy: ClusterFirstWithHostNet                    # dns策略

 

 

StatefulSet 类比ReplicaSet,用于部署有状态服务的pod
DaemonSet(ds) ds能够在每个节点上都部署pod,而不是指定数量,一般用于系统初始化等全局设置。ds也可以指定selector,这样就在指定的节点上部署pod。
Job和CronJob 不同于pod的持续运行,Job/CronJob只运行一次/定时执行,也可以指定执行次数和并发度。
PersistentVolume(pv)和PersistentVolumeClaim(pvc)

用于持久化存储

local模式:指定HostPath type的volume,然后通过volumeMount挂载到容器

非local模式:运维人员事先创建好一系列不同类型的pv(如gcePersistentDisk,awsElasticBlockStore,azureDisk,cinder,cephfs),在pvc中定义pv的大小和读写模式,然后pod中就可以直接引用,再通过volumeMount挂载到容器,好处是解耦了实际存储方案。

Horizontal Pod Autoscaler(hpa)

关联到某个deployment,检测pod的运行资源占用情况(cpu、内存,也可以自定义),实现动态扩缩容,最终尽量接近pod的资源申请值。
ConfigMap(cm) 本质上是一种卷(volume),用于应用配置抽离,通过卷挂载或者环境变量的方式注入到容器中
ServiceAccount

k8s集群内身份,每个命名空间如果没有手动创建,则会默认创建一个名为‘default’的sa。

授权基于RBAC,通过roleBinding绑定到role,或通过clusterRoleBinding绑定到clusterRole来赋予权限。role和clusterRole的区别是,role是某个命名空间下的资源,clusterRole是集群维度的。


k8s集群架构

 


控制平面组件

控制平面组件包括Kubernetes API server、etcd分布式持久存储、调度器、控制其管理器

组件

知识点

Kubernetes API server
  • 功能:提供一组RestAPI,用于资源CRUD,是跟数据持久化组件etcd唯一交互的入口,其他组件都是通过请求API server来管理资源。
  • 观察者模式:提供观察者模式实现资源变更监听,监听注册接口是GET /.../.../?watch=true,url为监听资源路径,只需要一个param。
  • 认证鉴权:API server通过一些插件来实现认证、鉴权、格式校验。全部通过后才能操作资源。
  • 高可用:API server无状态的,只要多节点部署即可
  • 一致性:通过乐观锁保证同一时刻只有一个API server的写请求能够被处理,所有的Kubernetes资源都有一个metadata.resourceVersion字段,如果更新操作携带的version不是最新的,就会被etcd拒绝;
etcd
  • 定义:etcd是一个分布式key-value存储系统;
  • 资源存储方案:资源统一存储在/registry目录下,按资源类别建立子目录,在按照命名空间建立下一层子目录,再按照资源名建立文件,一个典型的目录如下:
    /registry/pods/default/nginx-159041347-foipa
  • 高可用:多节点部署,基于raft算法保证数据一致性,因此需要有奇数个节点。

扩展:raft算法是一种分布式一致性算法,更新操作需要在集群内大多数节点同意才能进入下个状态。出现脑裂的情况下,多数节点的集群能够继续更新,少数节点的集群会停止更新并保持在当前状态。

调度器
  • 功能:调度器负责协调资源,监听资源创建事件。
  • pod调度:当需要创建pod时,调度器根据一系列的约束条件,选择合适的node,反馈给API server,由API server创建资源;
  • 与kubelet解耦:虽然调度器负责pod的选择,kubelet负责pod的实际部署,但二者并不直接通信,而是由kubelet监听pod变更事件来实现;
  • 高可用:调度器也可以在集群内多副本,但由于调度器一直监听资源变更,多副本同时工作可能会出现状态异常(例如创建多个pod而不是1个),所以多个副本中只有一个副本在工作,其他都在等主宕机然后竞选
  • 调度器选主:调度器的多个副本竞选主节点,是基于资源(kube-scheduler Endpoint,专用于调度器选主)的乐观锁实现,竞选操作会去更新该资源的master node字段,竞选成功之后就要每2s发更新资源操作以通知自身存活,副本在没收到健康告知时会再次发起竞选。
控制器管理器
  • 功能:控制器管理器提供了一组控制器,每个控制器负责管理一类资源,例如EndpointController,DeploymentController,以确保资源调度朝着目标方向收敛;
  • 控制器通过监听API server来管理各自的资源,它们相互之间甚至并不感知,独立与API server通信;
  • 高可用:调度器一样。

 

工作节点组件

组件

知识点

kubelet
  • 定义:kubelet是在工作节点上实际执行资源CRUD的worker;
  • node注册:当一个节点被纳入k8s集群管理时,kubelet就会通过API server创建node资源来注册当前节点,然后监听该node看是否有调度事件;
  • pod部署:当API server调度pod到该node时,会下发配置好的容器运行时给kubelet,然后kubelet开始具体的部署,之后kubelet会持续上传该pod的状态、资源占用等信息给API server;
  • 资源监控:kubelet包含了一个cAdvisor的agent,会采集节点上每个容器的资源使用情况,上报给API server;k8s提供了一个叫做Heapster的组件,对cAdvisor的采集信息做汇总、可视化等;
kube-proxy
  • 功能:kube-proxy监听service和endpoint资源,当资源变更时,选择一个可用pod的ip和端口,更新到本地的iptables,以确保对service的请求能够转发到一个有效的后端;
  • 一个请求的例子:console请求nginx,可以直接用http://nginx:35357的地址访问,因为容器的/etc/resolve.conf被k8s修改成指向kube-dns pod,它实现了service的域名解析。报文到系统内核处理时,会经过iptables,匹配到一条记录,service的ip和端口就被改写成了后端pod的ip和端口。


一次deploy的过程

 

 

一个pod 的部署流程
(1)客户端发起请求创建Deployment(如kubectl),API server 创建一个Deployment资源
(2)监听Deployment事件的Deployment Controller收到消息,请求API server创建ReplicaSet
(3)监听ReplicaSet事件的ReplicaController收到消息,请求API server创建pod
(4)监听pod创建事件的调度器收到消息,选择出合适的node发给API server
(5)监听node资源的kubelet收到消息,在本node上创建pod

 

k8s计算资源管理

问题一:pod如何描述自身所需的计算资源量?

在pod中可以指定requestslimits
资源requests是指定pod申请的系统资源,如CPU/内存量,跟pod实际使用量无关,即pod实际使用量可能大于、等于或小于requests,是给调度器使用的。
资源limits是资源实际使用量,用于避免某个pod占用过多系统资源。k8s调度时可能超售。当pod使用比limits更多的CPU时不会被kill,但使用比limits更多的内存时会被kill。
limits >= requests

apiVersion: apps/v1

kind: Deployment

metadata:

  name: nginx-deployment

spec:

  selector:

    matchLabels:

      app: nginx

  replicas: 1

  template:

    metadata:

      labels:

        app: nginx

    spec:

      containers:

      - name: nginx

        image: nginx:1.11

        ports:

        - containerPort: 80

        resources:

          limits:

            cpu: 2048m                      # 2048m表示2048个豪核,也就是2个整核

            memory: 2Gi                     # 2G内存

          requests:

            cpu: 2048m

            memory: 2Gi

 


问题二:系统资源不够时,k8s按什么机制kill pod?

QoS:按照requests和limits的关系,pod被分成了三档,没有指定requests和limits为BestEffort,指定了requests和limits且相等为Guaranteed,其他为Burstable。
kill顺序为BestEffort -> Burstable -> Guaranteed
如果QoS level一样,则根据使用率来kill,即实际使用量/requests量,较大的会先被kill(为什么是这样的策略呢?我理解requests是调度时分配的额度,比例越高意味着pod可用资源越接近枯竭,就更应该尽早重新调度);

 

 

问题三:是否有集群维度的统一限制?
有,通过创建LimitRange资源,可以做如下限制:
(1)pod可申请的最小资源
(2)pod可申请的最大资源
(3)container可申请的最大资源
(4)container未指定时分配的默认requests和limits
(5)request/limits比例的最大值
(6)container可申请的PVC最大值

apiVersion: v1

kind: LimitRange

metadata:

  name: mylimits

spec:

  limits:

  - max:

      cpu: "4"                      # cpu最大值

      memory: 2Gi                   # 内存最大值

    min:

      cpu: 200m                     # cpu最小值

      memory: 6Mi                   # 内存最小值

    maxLimitRequestRatio:           # limit/request最大比例

      cpu: 3

      memory: 2

    type: Pod                       # pod维度的限制

  default:

      cpu: 300m

      memory: 200Mi

    defaultRequest:

      cpu: 200m

      memory: 100Mi

    max:

      cpu: "2"

      memory: 1Gi

    min:

      cpu: 100m

      memory: 3Mi

    maxLimitRequestRatio:

      cpu: 5

      memory: 4

    type: Container                 # 容器维度的限制

 

 

问题四:是否可以在namespace维度限制quota?是否可以限制pod创建数量?
可以,通过创建ResourceQuota资源,可以限制命名空间所有pod可使用的cpu、内存最大值。还可以限制各种资源的创建量,如pod、ReplicationController等。

apiVersion: v1

kind: ResourceQuota

metadata:

  namespace: mynamespace

  name: mynamespace

  labels:

    project: myproject

    app: resourcequota

    version: v1

spec:

  hard:

    pods: 50

    requests.cpu: 4

    requests.memory: 2Gi

    limits.cpu: 8

    limits.memory: 4Gi

    configmaps: 20

    persistentvolumeclaims: 20

    replicationcontrollers: 20

    secrets: 20

    services: 50

 

pod的安全机制

问题一:pod内运行的container是相互隔离的,那pod之间有绝对的网络、权限安全性吗?
并没有,需要限制。
从几个方面考虑这个问题,首先,pod可以直接使用宿主机的命名空间的(详见问题二),意味着有能力在pod内部看到宿主机的进程、网络流量,乃至直接与非本pod内的container进行进程间通信。其次,pod默认启动用户是root,结合上一条可知,如果不加限制,是有能力以root身份操作宿主机的。

 

问题二:pod如何使用宿主机的命名空间?
网络命名空间:pod描述文件中指定hostNetwork:true
绑定宿主机端口:pod描述文件中指定hostPort(网络namespace还是跟宿主机隔离的)
PID命名空间:pod描述文件中指定hostPID: true
IPC命名空间:pod描述文件中指定hostIPC: true

apiVersion: v1

kind: Pod

metadata:

  name: nginx                   # pod名

  labels:

    name: nginx                 # pod标签

spec:

  hostNetwork: true

  hostPort: true

  hostPID: true

  hostIPC: true

 

问题三:如何在pod描述中增加限制,避免镜像被篡改时可能出现越权操作?
解决方案是securityContext资源:securityContext可以在pod级别对所有container生效,也可以在container级别指定。在securityContext里可以做这些约束:
(1)指定容器运行进程的用户ID:runAsUser(指定UID)
(2)阻止以root运行:runAsNonRoot(不care是哪个用户运行)
(3)指定特权模式:privileged(kube-proxy就是一个需要使用privileged=true的例子,因为要修改iptables等)
(4)单独指定权限:capabilities里指定add/drop内核功能(例如drop SYS_TIME禁止容器修改系统时间)
(5)阻止根文件系统写入:readOnlyRootFileSystem(对具体要写的卷再单独指定readOnly=false)

apiVersion: apps/v1beta2

kind: Deployment

metadata:

  name: nginx

spec:

  replicas: 1                                               # 副本数量

  selector:

    matchLabels:

      app: nginx                                      # 管理的pod的标签

  template:                                                

    metadata:

      labels:

        app: nginx

      securityContext:                                     

        runAsNonRoot: true                                  # 限制不能以root身份运行

        runAsUser: 601                                      # 以601用户身份运行

 

 

问题四:上面的方案都是依赖pod自己遵守规定,有没有集群维度的统一管理?
有,通过创建PodSecurityPolicy资源,它可以指定securityContext里能做到的一切。

首先创建PodSecurityPolicy(psp),然后创建role,关联此psp,然后再创建roleBinding资源,将此policy赋权给具体的serviceAccount。

apiVersion: policy/v1beta1

kind: PodSecurityPolicy

metadata:

  name: example

spec:

  privileged: false  # Don't allow privileged pods!

  # The rest fills in some required fields.

  seLinux:

    rule: RunAsAny

  supplementalGroups:

    rule: RunAsAny

  runAsUser:

    rule: RunAsAny

  fsGroup:

    rule: RunAsAny

k8s与juju对比

对juju了解不多,仅限于使用,因此只在使用层面上进行对比。

 

k8s

juju

 

k8s

juju

描述文件 一组yaml文件 charm包
部署

kubectl create pod/replicaset/deployment -f xxx.yaml

无需关心底层节点,在描述文件里指定tag和副本数量

juju deploy ${appDir} -n 3 --to 7,8,9

需要了解节点编号

升级 deployment有变更时会自动执行,能够记录历史变更记录,kubectl rollout就可以快速回滚(还可以指定回滚目标版本) 需要手动执行juju upgrade,无历史记录
扩容 修改deployment replica数量,就能够自动扩容 手动执行juju add-unit docker-nginx -n 3 --to 7,8,9
探活 支持自定义livenessProbe(Exec / HTTP GET / TCP socket) 没有探活机制,只监测docker存活状态。
组件间依赖 在initContainer里检测依赖服务是否正常,每次部署都会检测;

在metadata.yaml里指定requires服务,只有首次部署会自动添加,后续修改依赖不会自动添加,需要手动执行juju add-relation/juju remove-relation

hook

支持Post-start hook(跟容器启动程序是异步的)和pre-stop hook(在容器终止之前执行) charm包里也可以指定多种stage hook
进入容器 底层抽象的更加彻底,直接在集群内任意机器上执行kubectl exec -it ${podName} /bin/sh 需要登陆对应宿主机,再docker exec -it ${dockerName} /bin/sh
配置文件 通过init container挂载;通过configMap注入; 卷挂载方式注入容器
     

 

相关资料

https://etcd.io/

https://www.kubernetes.org.cn/k8s

https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands

https://github.com/kubernetes/kubernetes

https://helm.sh/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章