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/

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