1. 程式人生 > 實用技巧 >基於ambassador實現K8S灰度釋出

基於ambassador實現K8S灰度釋出

為什麼需要灰度釋出#

灰度釋出(又名金絲雀釋出)是指在黑與白之間,能夠平滑過渡的一種釋出方式。在其上可以進行A/B testing,即讓一部分使用者繼續用產品特性A,一部分使用者開始用產品特性B,如果使用者對B沒有什麼反對意見,那麼逐步擴大範圍,把所有使用者都遷移到B上面來。

總結下一些應用場景:

  • 微服務依賴很多元件,需要在實際環境驗證
  • 部署新功能有風險,然後可以通過導流一小部分使用者實際使用,來減小風險
  • 讓特定的使用者訪問新版本,比如部署一個版本,只讓測試使用
  • A/B Testing,部署兩個版本,進行版本對比,比如驗證兩個推薦服務的推薦效果

灰度釋出可以保證整體系統的穩定,在初始灰度的時候就可以發現、調整問題,以保證其影響度。

ambassador介紹#

ambassador[æmˈbæsədər],是Kubernetes微服務 API gateway,基於Envoy Proxy。

Open Source Kubernetes-Native API Gateway built on the Envoy Proxy

官方地址:

https://www.getambassador.io/

部署ambassador#

按官網提示部署ambassador

Copy
cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: Service
metadata:
  labels:
    service: ambassador-admin
  name: ambassador-admin
spec:
  type: NodePort
  ports:
  - name: ambassador-admin
    port: 8877
    targetPort: 8877
  selector:
    service: ambassador
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: ambassador
rules:
- apiGroups: [""]
  resources: [ "endpoints", "namespaces", "secrets", "services" ]
  verbs: ["get", "list", "watch"]
- apiGroups: [ "getambassador.io" ]
  resources: [ "*" ]
  verbs: ["get", "list", "watch"]
- apiGroups: [ "apiextensions.k8s.io" ]
  resources: [ "customresourcedefinitions" ]
  verbs: ["get", "list", "watch"]
- apiGroups: [ "networking.internal.knative.dev" ]
  resources: [ "clusteringresses", "ingresses" ]
  verbs: ["get", "list", "watch"]
- apiGroups: [ "networking.internal.knative.dev" ]
  resources: [ "ingresses/status", "clusteringresses/status" ]
  verbs: ["update"]
- apiGroups: [ "extensions" ]
  resources: [ "ingresses" ]
  verbs: ["get", "list", "watch"]
- apiGroups: [ "extensions" ]
  resources: [ "ingresses/status" ]
  verbs: ["update"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: ambassador
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: ambassador
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: ambassador
subjects:
- kind: ServiceAccount
  name: ambassador
  namespace: kube-system
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: authservices.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: authservices
    singular: authservice
    kind: AuthService
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: consulresolvers.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: consulresolvers
    singular: consulresolver
    kind: ConsulResolver
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: kubernetesendpointresolvers.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: kubernetesendpointresolvers
    singular: kubernetesendpointresolver
    kind: KubernetesEndpointResolver
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: kubernetesserviceresolvers.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: kubernetesserviceresolvers
    singular: kubernetesserviceresolver
    kind: KubernetesServiceResolver
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: mappings.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: mappings
    singular: mapping
    kind: Mapping
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: modules.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: modules
    singular: module
    kind: Module
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: ratelimitservices.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: ratelimitservices
    singular: ratelimitservice
    kind: RateLimitService
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tcpmappings.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: tcpmappings
    singular: tcpmapping
    kind: TCPMapping
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tlscontexts.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: tlscontexts
    singular: tlscontext
    kind: TLSContext
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: tracingservices.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: tracingservices
    singular: tracingservice
    kind: TracingService
    categories:
    - ambassador-crds
---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: logservices.getambassador.io
spec:
  group: getambassador.io
  version: v1
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: logservices
    singular: logservice
    kind: LogService
    categories:
    - ambassador-crds
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ambassador
spec:
  replicas: 3
  selector:
    matchLabels:
      service: ambassador
  template:
    metadata:
      annotations:
        sidecar.istio.io/inject: "false"
        
"consul.hashicorp.com/connect-inject": "false" labels: service: ambassador spec: affinity: podAntiAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 podAffinityTerm: labelSelector: matchLabels:
service: ambassador topologyKey: kubernetes.io/hostname serviceAccountName: ambassador containers: - name: ambassador image: quay.azk8s.cn/datawire/ambassador:0.86.1 resources: limits: cpu: 1 memory: 400Mi requests: cpu: 200m memory: 100Mi env: - name: AMBASSADOR_NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace ports: - name: http containerPort: 8080 - name: https containerPort: 8443 - name: admin containerPort: 8877 livenessProbe: httpGet: path: /ambassador/v0/check_alive port: 8877 initialDelaySeconds: 30 periodSeconds: 3 readinessProbe: httpGet: path: /ambassador/v0/check_ready port: 8877 initialDelaySeconds: 30 periodSeconds: 3 volumeMounts: - name: ambassador-pod-info mountPath: /tmp/ambassador-pod-info volumes: - name: ambassador-pod-info downwardAPI: items: - path: "labels" fieldRef: fieldPath: metadata.labels restartPolicy: Always securityContext: runAsUser: 8888 --- apiVersion: v1 kind: Service metadata: name: ambassador spec: type: NodePort externalTrafficPolicy: Local ports: - port: 80 targetPort: 8080 selector: service: ambassador EOF

為了方便訪問閘道器,生成一個ingress:


Copy
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
 annotations:
   nginx.ingress.kubernetes.io/proxy-body-size: "0"
   nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
   nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
   kubernetes.io/tls-acme: 'true'
 name: ambassador
spec:
 rules:
 - host: ambassador.iflyresearch.com
   http:
     paths:
     - backend:
         serviceName: ambassador
         servicePort: 80
       path: /

ambassador 配置#

ambassador 使用envoy來實現相關的負載,而envoy類似nginx。ambassador的原理大概是讀取service裡的配置,然後自動生成envoy的配置,當service變更時,動態更新envoy的配置並重啟,所以ambassador需要可以訪問服務API。

ambassador 的配置是放到metadata的annotations,以getambassador.io/config開頭:

Copy
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  {{ .Values.service.name }}_mapping
      prefix: /{{ .Values.service.prefix }}
      service: {{ .Values.service.name }}.{{ .Release.Namespace }}

profix指定如何訪問服務,service指定指向那個服務。注意,需要加上namespace名稱,否則容易報找不到後端。

ambassador 灰度#

ambassador實現灰度可以根據weight權重,或者指定匹配特定的header來實現。

根據weight進行灰度#

用法:

部署一個新版本的service,prefix和之前老服務保持一致,但是配置weight,比如20,這樣20%的流量會流轉到新服務,這樣實現A/B Test

Copy
---
apiVersion: v1
kind: Service
metadata:
  name: svc-gray
  namespace: default
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  svc1_mapping
      prefix: /svc/
      service: service-gray
	  weight: 20
spec:
  selector:
    app: testservice
  ports:
  - port: 8080
    name: service-gray
    targetPort: http-api

根據請求頭 header 進行灰度 (regex_headers 正則匹配)#

部署一個新版本,只需要特定的使用者才能訪問,可以通過該方案來實現。

例如:

Copy
---
apiVersion: v1
kind: Service
metadata:
  name: svc-gray
  namespace: default
  annotations:
    getambassador.io/config: |
      ---
      apiVersion: ambassador/v0
      kind:  Mapping
      name:  svc1_mapping
      prefix: /svc/
      service: service-gray
	  headers:
        gray: true
spec:
  selector:
    app: testservice
  ports:
  - port: 8080
    name: service-gray
    targetPort: http-api

訪問時,當指定gray: true時,訪問灰度版本,可以用postman來測試: