Spinnaker第四節-對接k8s

前一篇我們介紹了Spinnaker是如何對接和管理實例雲的,本篇我們將介紹Spinnaker如何對接k8s

Spinnaker在對接實例雲時已經爲我們做過很好的鋪墊,讓我們接觸到Immutable的發佈方式,並體驗到隨之而來的好處。K8s將Immutable發揮到極致,將版本的創建與銷燬由分鐘級提高到秒級。

 

Spinnaker管理K8S的前世今生

Spinnaker曾經按照管理實例的方式推出過一版對接k8s的管理機制,我們這裏叫他V1,設計思路與上一篇中的pipeline一模一樣,也是拆分爲bake、deploy、scaledown、destroy等。

後來Spinnaker推出了V2版,也就是現在主推的版本,基於manifest的管理思路,以聲明式的設計徹底顛覆了V1版的思想,使pipeline變得簡單高效、便於管理。

以下所有內容都是基於V2版本展開介紹。

 

Spinnaker對接K8S的條件

必要條件:config+kubelet

Spinnaker管理雲平臺肯定需要配置認證信息來得到雲平臺的授權,K8S也不例外,對於K8S的授權配置只需要在.kube目錄下存放容器雲的config認證文件既可,跟aws有點類似,aws的認證是將key和secret存放在.aws目錄下。

Spinnaker操作實例雲平臺是通過SDK,代碼中直接調用雲平臺的各個接口;而Spinnkaer操作K8S是通過kubelet,代碼轉化成本地的kubelet命令的方式來實現的。所以一定要在spinnaker部署的機器上預先安裝好kubelet。

推薦條件:交付倉庫(Artifact)

Spinnaker支持S3、Http、Github、Gitlab等各種交付倉庫,在對接K8S時交付倉庫主要用來存放manifest文件的。雖然Spinnaker的Pipeline中可以直接讀寫manifest,如下圖:

但是不適用於代碼配置分離或者企業具有CMDB的場景,我們只讓spinnaker運行manifest,而自身並不維護manifest時,將manifest的配置和管理交給Artifact,將採用這種方式:

Spinnaker如何幹預K8S

瞭解K8S的同學都知道,manifest是聲明式的,不管現狀是怎樣的,manifest文件中代表的是最終的目的和結果。對於manifest我計劃下個月寫一篇博文詳細解讀下,這裏先看一個簡單的例子:

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    artifact.spinnaker.io/location: devops
    artifact.spinnaker.io/name: ci-gateway
    artifact.spinnaker.io/type: kubernetes/deployment
    moniker.spinnaker.io/application: ci
    moniker.spinnaker.io/cluster: ci-gateway
    strategy.spinnaker.io/max-version-history: '1'
    strategy.spinnaker.io/use-source-capacity: 'true'
  labels:
    app.kubernetes.io/managed-by: spinnaker
    app.kubernetes.io/name: ci-gateway
  name: ci-gateway
  namespace: devops
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: ci-gateway
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
        artifact.spinnaker.io/location: devops
        artifact.spinnaker.io/name: ci-gateway
        artifact.spinnaker.io/type: kubernetes/deployment
        moniker.spinnaker.io/application: ci
        moniker.spinnaker.io/cluster: ci-gateway
      labels:
        app.kubernetes.io/managed-by: spinnaker
        app.kubernetes.io/name: ci-gateway
        configMap.version: '${ parameters.config_version}'
        k8s-app: ci-gateway
        task: monitoring
    spec:
      containers:
        - args:
            - '--spring.config.location=application.yml'
          image: 'hub.imgo.tv/spinnaker/gateway:${ parameters.image_version}'
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /
              port: 9000
              scheme: HTTP
            initialDelaySeconds: 30
            timeoutSeconds: 5
          name: ci-gateway
          volumeMounts:
            - mountPath: /usr/lib/application.yml
              name: app-conf
              subPath: application.yml
            - mountPath: /usr/lib/iplist.txt
              name: app-conf
              subPath: iplist.txt
      volumes:
        - configMap:
            items:
              - key: application.yml
                path: application.yml
              - key: iplist.txt
                path: iplist.txt
            name: 'ci-gateway-configmap-${ parameters.config_version}'
          name: app-conf

這是一個deployment的manifest,採用滾筒發佈,其中${}內是spinnaker的pipeline中定義的一些動態變量。

那麼問題來了,Spinnaker最高理想是全自動,pipeline在初次配置後不需要再維護了,假如我生產某個replicaSet配有自動容縮的能力,一旦需要更換鏡像豈不是被manifest強行恢復到pipeline中預設的副本數了麼?Spinnaker的設計者早就想到了這個問題,解決思路是通過annotation。

strategy.spinnaker.io/use-source-capacity這個annotation就是爲了專門解決上面的問題而設計的,默認爲false,如果配置爲true代表着直接使用replicaSet現有的容量,忽視manifest中配置的副本數!

類似的annotation還有很多,這一個個annotation解決了對接中的很多痛點。

moniker.spinnaker.io/application:資源屬於哪個app

moniker.spinnaker.io/cluster:資源屬於哪個cluster

strategy.spinnaker.io/max-version-history:replicaSet保留幾個歷史版本

strategy.spinnaker.io/recreate:deployment的時候是否每次都要重建pod

我猜想它的原理有點像Java開發Spring的AOP,有一個切面就是專門處理各種annotation的,有些annotation是前置加強,有些是後續加強,有些是循環加強。像strategy.spinnaker.io/use-source-capacity這個annotation應該就是前置加強了,spinnaker看到manifest中有這個annotation,先去k8s中獲取到真實的容量,然後覆蓋掉manifest中的副本數,纔有了我們現在看到的這種“反聲明式”的神奇效果。

 

Spinnaker管理K8S初級pipeline

這裏所謂初級,就是代碼和配置在一起的獨立鏡像,spinnaker只負責deployment就好,不需要關心鏡像內容。Pipeline如下

圖中有2個service,test_service爲測試環境提供服務,product_service爲生產環境提供服務。

圖中有4條pipeline,前兩條爲測試pipeline,第三條爲灰度發佈pipeline,第四條爲生產發佈pipeline。以下是詳細介紹:

第一條:測試Pipeline

manifest掛在外存儲中,spinnaker自身不關心資源文件,當外存儲manifest發生變化時通過webhook自動觸發測試環境更新操作。

Start:傳入包含manifest文件的git配置,由git的webhook來自動觸發

Deployment:

第二條:測試Pipeline(推薦)

manifest在spinnaker中維護,外系統傳入鏡像tag觸發測試環境鏡像更新操作。

Start:傳入鏡像的tag,可以由webhook來自動觸發,也可以被Jenkins任務觸發

Deployment:

第三條:灰度Pipeline

觸發時傳入鏡像tag,新建副本數1的一個replicaSet掛載到生產service,觀察結束後銷燬灰度set,根據灰度觀察結果選擇是否觸發生產Pipeline

Destroy環節:

根據灰度deployment時設置的標籤,當灰度結束後可以刪除這些pod

觸發生產的Pipeline:

當灰度發佈結果爲yes時,觸發下一級pipeline,並將鏡像tag參數透傳下去。

第四條:生產Pipeline

觸發時傳入鏡像tag,按照生產現有副本數創建replicaSet,採用RollingUpdate的方式更新pod

這一步就很簡單了,注意兩點。

1 Deployment採用RollingUpdate的發佈方式

2 strategy.spinnaker.io/use-source-capacity: 'true'這個annotation保證生產pod的容量。

 

Spinnaker對接K8S高階pipeline

像我們這種有CMDB的企業,配置和代碼都是分離的,研發只負責開發代碼,敏感信息和軟件配置信息是由業務運維來維護的。這種場景就需要用到K8S的configMap和Secret,Pipeline的設計和產品發佈系統就需要重新設計,如下:

設計上的改變:

1 Harbor中鏡像tag要顯示的區分是內測版、公測版、發佈版本

2 configMap和secret需要區分測試環境還是生產環境,其中configMap需要有版本的概念,secret無需版本的概念

其中ConfigMap和Secret的manifest直接通過http方式向cmdb獲取,spinnaker本身來維護deployment的manifest。

Start:傳入鏡像tag和configMap版本動態參數,同時向cmdb去索取configMap和secret的manifest。可以由jenkins觸發、harbor觸發、webhook觸發。

獲取configMap:

獲取secret:

動態參數:

Deploy ConfigMap:

Deploy Secret:

Deploy Pod:

配置詳情:

secret的manifest:

apiVersion: v1
data:
  youdu_appId: my_youdu_app_id
  youdu_buin: my_youdu_buin
  youdu_encodingaesKey: my_youdu_encodingaes_key
kind: Secret
metadata:
  name: ci-youdu-secret
  namespace: devops
type: Opaque

configMap的manifest:

apiVersion: v1
kind: ConfigMap
metadata:
  name: ci-youdu-configmap-v1
  namespace: devops
data:
  application.yml: |
    server:
      port: 8098
    spring:
      application:
        name: ci-youdu 
    eureka:
      instance:
        preferIpAddress: true
        instanceId: ${spring.cloud.client.ipAddress}:${server.port}
        hostname: ci-register
      client:
        serviceUrl:
          defaultZone: http://ci-register:8111/eureka/
    youdu:
      address: im.imgo.tv:7080 
    swagger:
      enabled: true

deployment的manifest:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  annotations:
    artifact.spinnaker.io/location: devops
    artifact.spinnaker.io/name: ci-youdu
    artifact.spinnaker.io/type: kubernetes/deployment
    moniker.spinnaker.io/application: ci
    moniker.spinnaker.io/cluster: ci-youdu
    strategy.spinnaker.io/max-version-history: '1'
    strategy.spinnaker.io/use-source-capacity: 'true'
  labels:
    app.kubernetes.io/managed-by: spinnaker
    app.kubernetes.io/name: ci-youdu
  name: ci-youdu
  namespace: devops
spec:
  replicas: 1
  selector:
    matchLabels:
      k8s-app: ci-youdu
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      annotations:
        artifact.spinnaker.io/location: devops
        artifact.spinnaker.io/name: ci-youdu
        artifact.spinnaker.io/type: kubernetes/deployment
        moniker.spinnaker.io/application: ci
        moniker.spinnaker.io/cluster: ci-youdu
      labels:
        app.kubernetes.io/managed-by: spinnaker
        app.kubernetes.io/name: ci-youdu
        configMap.version: '${ parameters.config_version}'
        k8s-app: ci-youdu
        task: monitoring
    spec:
      containers:
        - args:
            - '--spring.config.location=application.yml'
          env:
            - name: youdu_appId
              valueFrom:
                secretKeyRef:
                  key: youdu_appId
                  name: ci-youdu-secret
            - name: youdu_buin
              valueFrom:
                secretKeyRef:
                  key: youdu_buin
                  name: ci-youdu-secret
            - name: youdu_encodingaesKey
              valueFrom:
                secretKeyRef:
                  key: youdu_encodingaesKey
                  name: ci-youdu-secret
          image: 'hub.imgo.tv/spinnaker/youdu:${ parameters.image_version}'
          imagePullPolicy: IfNotPresent
          livenessProbe:
            httpGet:
              path: /
              port: 8098
              scheme: HTTP
            initialDelaySeconds: 30
            timeoutSeconds: 5
          name: ci-youdu
          volumeMounts:
            - mountPath: /usr/lib/application.yml
              name: app-conf
              subPath: application.yml
      volumes:
        - configMap:
            items:
              - key: application.yml
                path: application.yml
            name: 'ci-youdu-configmap-${ parameters.config_version}'
          name: app-conf

Spinnaker對接K8S的不足

Spinnaker對於K8S的定位是持續部署工具,並不是核心的K8S管理工具,比起rancher這種專業的工具在管理上存在很大的差距。例如鏡像授權、namespace管理、用戶管理、性能監控、容器日誌輸出和控制檯登陸等spinnaker都不支持。所以對於spinnaker的定位很重要,如果你們企業缺少一個自動發佈的持續部署工具,請選擇spinnaker;如果你們企業缺少一個專業的K8S管理工具,請選擇Rancher。當然你可以兩個都選(我們公司就是這麼用的),因爲spinnaker與rancher是可以兼容的,因爲它們的數據來源都來自K8S本身。Spinnaker負責管理pod、service、ingress和發佈流程,rancher負責管理namespace等其它spinnaker管理不了的資源。

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