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,如下图:

Spinnaker第四节-对接k8s_第1张图片

但是不适用于代码配置分离或者企业具有CMDB的场景,我们只让spinnaker运行manifest,而自身并不维护manifest时,将manifest的配置和管理交给Artifact,将采用这种方式:

Spinnaker第四节-对接k8s_第2张图片

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如下

Spinnaker第四节-对接k8s_第3张图片

图中有2个service,test_service为测试环境提供服务,product_service为生产环境提供服务。

图中有4条pipeline,前两条为测试pipeline,第三条为灰度发布pipeline,第四条为生产发布pipeline。以下是详细介绍:

第一条:测试Pipeline

manifest挂在外存储中,spinnaker自身不关心资源文件,当外存储manifest发生变化时通过webhook自动触发测试环境更新操作。

Start:传入包含manifest文件的git配置,由git的webhook来自动触发

Spinnaker第四节-对接k8s_第4张图片

Spinnaker第四节-对接k8s_第5张图片

Deployment:

Spinnaker第四节-对接k8s_第6张图片

第二条:测试Pipeline(推荐)

manifest在spinnaker中维护,外系统传入镜像tag触发测试环境镜像更新操作。

Start:传入镜像的tag,可以由webhook来自动触发,也可以被Jenkins任务触发

Spinnaker第四节-对接k8s_第7张图片

Deployment:

Spinnaker第四节-对接k8s_第8张图片

第三条:灰度Pipeline

触发时传入镜像tag,新建副本数1的一个replicaSet挂载到生产service,观察结束后销毁灰度set,根据灰度观察结果选择是否触发生产Pipeline

Destroy环节:

Spinnaker第四节-对接k8s_第9张图片

根据灰度deployment时设置的标签,当灰度结束后可以删除这些pod

触发生产的Pipeline:

Spinnaker第四节-对接k8s_第10张图片

当灰度发布结果为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的设计和产品发布系统就需要重新设计,如下:

Spinnaker第四节-对接k8s_第11张图片

设计上的改变:

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:

Spinnaker第四节-对接k8s_第12张图片

获取secret:

Spinnaker第四节-对接k8s_第13张图片

动态参数:

Spinnaker第四节-对接k8s_第14张图片

Deploy ConfigMap:

Spinnaker第四节-对接k8s_第15张图片

Deploy Secret:

Spinnaker第四节-对接k8s_第16张图片

Deploy Pod:

Spinnaker第四节-对接k8s_第17张图片

配置详情:

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管理不了的资源。

你可能感兴趣的:(Spinnaker)