前一篇我们介绍了Spinnaker是如何对接和管理实例云的,本篇我们将介绍Spinnaker如何对接k8s
Spinnaker在对接实例云时已经为我们做过很好的铺垫,让我们接触到Immutable的发布方式,并体验到随之而来的好处。K8s将Immutable发挥到极致,将版本的创建与销毁由分钟级提高到秒级。
Spinnaker曾经按照管理实例的方式推出过一版对接k8s的管理机制,我们这里叫他V1,设计思路与上一篇中的pipeline一模一样,也是拆分为bake、deploy、scaledown、destroy等。
后来Spinnaker推出了V2版,也就是现在主推的版本,基于manifest的管理思路,以声明式的设计彻底颠覆了V1版的思想,使pipeline变得简单高效、便于管理。
以下所有内容都是基于V2版本展开介绍。
Spinnaker管理云平台肯定需要配置认证信息来得到云平台的授权,K8S也不例外,对于K8S的授权配置只需要在.kube目录下存放容器云的config认证文件既可,跟aws有点类似,aws的认证是将key和secret存放在.aws目录下。
Spinnaker操作实例云平台是通过SDK,代码中直接调用云平台的各个接口;而Spinnkaer操作K8S是通过kubelet,代码转化成本地的kubelet命令的方式来实现的。所以一定要在spinnaker部署的机器上预先安装好kubelet。
Spinnaker支持S3、Http、Github、Gitlab等各种交付仓库,在对接K8S时交付仓库主要用来存放manifest文件的。虽然Spinnaker的Pipeline中可以直接读写manifest,如下图:
但是不适用于代码配置分离或者企业具有CMDB的场景,我们只让spinnaker运行manifest,而自身并不维护manifest时,将manifest的配置和管理交给Artifact,将采用这种方式:
了解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只负责deployment就好,不需要关心镜像内容。Pipeline如下
图中有2个service,test_service为测试环境提供服务,product_service为生产环境提供服务。
图中有4条pipeline,前两条为测试pipeline,第三条为灰度发布pipeline,第四条为生产发布pipeline。以下是详细介绍:
manifest挂在外存储中,spinnaker自身不关心资源文件,当外存储manifest发生变化时通过webhook自动触发测试环境更新操作。
Start:传入包含manifest文件的git配置,由git的webhook来自动触发
Deployment:
manifest在spinnaker中维护,外系统传入镜像tag触发测试环境镜像更新操作。
Start:传入镜像的tag,可以由webhook来自动触发,也可以被Jenkins任务触发
Deployment:
触发时传入镜像tag,新建副本数1的一个replicaSet挂载到生产service,观察结束后销毁灰度set,根据灰度观察结果选择是否触发生产Pipeline
Destroy环节:
根据灰度deployment时设置的标签,当灰度结束后可以删除这些pod
触发生产的Pipeline:
当灰度发布结果为yes时,触发下一级pipeline,并将镜像tag参数透传下去。
触发时传入镜像tag,按照生产现有副本数创建replicaSet,采用RollingUpdate的方式更新pod
这一步就很简单了,注意两点。
1 Deployment采用RollingUpdate的发布方式
2 strategy.spinnaker.io/use-source-capacity: 'true'这个annotation保证生产pod的容量。
像我们这种有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:
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
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
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的定位是持续部署工具,并不是核心的K8S管理工具,比起rancher这种专业的工具在管理上存在很大的差距。例如镜像授权、namespace管理、用户管理、性能监控、容器日志输出和控制台登陆等spinnaker都不支持。所以对于spinnaker的定位很重要,如果你们企业缺少一个自动发布的持续部署工具,请选择spinnaker;如果你们企业缺少一个专业的K8S管理工具,请选择Rancher。当然你可以两个都选(我们公司就是这么用的),因为spinnaker与rancher是可以兼容的,因为它们的数据来源都来自K8S本身。Spinnaker负责管理pod、service、ingress和发布流程,rancher负责管理namespace等其它spinnaker管理不了的资源。