Kubernetes 이야기

Flagger를 사용한 CD ( Continuous Deployment ) 구축하기 본문

Kubernetes/devops

Flagger를 사용한 CD ( Continuous Deployment ) 구축하기

kmaster 2022. 2. 23. 21:58
반응형

Flagger

Flagger는 Kubernetes에서 실행되는 애플리케이션의 릴리스 프로세스를 자동화하는 CD 도구이다. Flagger는 현재 CNCF Incubating 프로젝트로서 사용자 요청 결과 (성공/실패) 메트릭을 자동으로 측정하고 설정한 임계치 미만으로 안정된 트래픽이 유지되면 새 버전으로 점진적으로 이동하여 프로덕션에 적용할 수 있는 솔루션이다.

 

Flagger는 GitOps 도구중 하니인 Flux 제품의 일부분인 프로젝트이다.

 

Flagger 는 배포를 진행할때 Blue/Green 또는 Canary 배포를 진행함에 있어 라우팅을 담당하는 여러 공급자와 연계하여 진행하게 된다.

 

대표적으로 아래의 공급자와 연계 가능하다.

 

Service mesh

  • Istio
  • Linkerd
  • AWS App Mesh
  • Open Service Mesh
  • Kuma

Ingress Controller

 

  • Nginx Ingress
  • Contour
  • Gloo
  • Skipper Ingress
  • Traefik

그럼 가장 대표적으로 많이 사용되는 Nginx Ingress와 연동하여 테스트를 진행해 보자.

 

설치

Flagger 는 Kubernetes v1.19 이상, Nginx ingress v1.0.2 이상이 필요하다. 

 

테스트환경

Kubernetes 1.21
Nginx ingress 1.1.1

 

1) Nginx Ingress Controller 설치

# helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# kubectl create ns ingress-nginx

# helm upgrade -i ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--set controller.hostNetwork=true \
--set controller.metrics.enabled=true \
--set controller.podAnnotations."prometheus\.io/scrape"=true \
--set controller.podAnnotations."prometheus\.io/port"=10254

 

2) Flagger 설치

# helm repo add flagger https://flagger.app

# helm upgrade -i flagger flagger/flagger \
--namespace ingress-nginx \
--set prometheus.install=true \
--set meshProvider=nginx

 

설치된 내용은 아래와 같다.

# kubectl get all -n ingress-nginx
NAME                                      READY   STATUS    RESTARTS   AGE
pod/flagger-695957cd69-g7v2k              1/1     Running   0          97s
pod/flagger-prometheus-7c7c88d987-428bj   1/1     Running   0          97s
pod/ingress-nginx-controller-7fnwh        1/1     Running   0          23m
pod/ingress-nginx-controller-kcthm        1/1     Running   0          23m
pod/ingress-nginx-controller-qfbw7        1/1     Running   0          23m

NAME                                         TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
service/flagger-prometheus                   ClusterIP   10.110.163.32    <none>        9090/TCP         97s
service/ingress-nginx-controller             ClusterIP   10.109.32.60     <none>        80/TCP,443/TCP   29m
service/ingress-nginx-controller-admission   ClusterIP   10.100.31.13     <none>        443/TCP          29m
service/ingress-nginx-controller-metrics     ClusterIP   10.108.187.140   <none>        10254/TCP        29m

NAME                                      DESIRED   CURRENT   READY   UP-TO-DATE   AVAILABLE   NODE SELECTOR            AGE
daemonset.apps/ingress-nginx-controller   3         3         3       3            3           kubernetes.io/os=linux   23m

NAME                                 READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/flagger              1/1     1            1           97s
deployment.apps/flagger-prometheus   1/1     1            1           97s

NAME                                            DESIRED   CURRENT   READY   AGE
replicaset.apps/flagger-695957cd69              1         1         1       97s
replicaset.apps/flagger-prometheus-7c7c88d987   1         1         1       97s

 

세팅 후 prometheus를 확인 해 보면 아래와 같이 데이터가 쌓이는 것을 볼수 있다. 

 

이제 앱을 배포해 보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-v1
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-v1
  template:
    metadata:
      labels:
        app: hello-v1
    spec:
      containers:
      - name: hello
        image: ghcr.io/kmaster8/hello:v1
        ports:
        - containerPort: 5050
---
apiVersion: v1
kind: Service
metadata:
  name: hello-v1
spec:
  type: ClusterIP
  selector:
    app: hello-v1
  ports:
  - protocol: TCP
    port: 5000
    targetPort: 5000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: prod-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
spec:
  rules:
  - host: test.prod.10.60.200.121.sslip.io
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: hello-v1
            port:
              number: 5000

 

앱을 test namespace에 배포하자. ( 주의할 점은 deployment name과 service name은 반드시 동일해야 한다. )

 

그리고 blue/green 배포 script를 만들어 보자.

 

apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: hello
  namespace: test
spec:
  provider: nginx
  # deployment reference
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hello-v1
  # ingress reference
  ingressRef:
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    name: prod-ingress
  # the maximum time in seconds for the canary deployment
  # to make progress before it is rollback (default 600s)
  progressDeadlineSeconds: 60
  service:
    # ClusterIP port number
    port: 5000
    # container port number or name
    targetPort: 5000
  analysis:
    # schedule interval (default 60s)
    interval: 10s
    iterations: 3
    # max number of failed metric checks before rollback
    threshold: 2
    # NGINX Prometheus checks
    metrics:
    - name: error-rate
      templateRef:
        name: error-rate
      thresholdRange:
        max: 0
      interval: 5s

 

위의 설정은 잠시 살펴보자

  1) hello-v1이라는 deployment를 배포

  2) prod-ingress 라는 이름의 ingress 사용

  3) service port는 5000번 사용

  4) 10초 간격으로 3번 체크. 2번 메트릭 정보를 조회 실패하면 롤백처리

  5) error-rate라는 template을 조회하여 임계치가 0일때만 blue/green 배포완료되도록 하고 , 에러율이 0이 아니면 롤백을 한다.

 

여기서 error-rate라는 template을 보자.

apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
  name: error-rate
  namespace: test
spec:
  provider:
    address: http://flagger-prometheus.ingress-nginx:9090
    type: prometheus
  query: |
    sum(rate(nginx_ingress_controller_requests{status=~"5.*|4.*",namespace="{{ namespace }}",service="{{ target }}"}[{{ interval }}])) /
    sum(rate(nginx_ingress_controller_requests{namespace="{{ namespace }}",service="{{ target }}"}[{{ interval }}])) * 100

 

이제 blue/green 배포 script를 실행하면 아래와 같이 deployment와 service의 상태가 변경된다.

 

# kubectl get all -n test
NAME                                    READY   STATUS    RESTARTS   AGE
pod/hello-v1-55575b9897-42q4f           1/1     Running   0          28s
pod/hello-v1-55575b9897-7pp9x           1/1     Running   0          28s
pod/hello-v1-55575b9897-w42vh           1/1     Running   0          28s
pod/hello-v1-primary-7479df5558-49scr   1/1     Running   0          10s
pod/hello-v1-primary-7479df5558-blrkr   1/1     Running   0          10s
pod/hello-v1-primary-7479df5558-zm47x   1/1     Running   0          10s

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                        AGE
service/hello-v1           ClusterIP   10.103.254.249   <none>        5000/TCP                                       94s
service/hello-v1-canary    ClusterIP   10.107.16.18     <none>        5000/TCP                                       10s
service/hello-v1-primary   ClusterIP   10.106.97.212    <none>        5000/TCP                                       10s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-v1            0/0     3            3           28s
deployment.apps/hello-v1-primary    3/3     3            3           10s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-v1-55575b9897            0         0         0       28s
replicaset.apps/hello-v1-primary-7479df5558    3         3         3       10s

NAME                       STATUS        WEIGHT   LASTTRANSITIONTIME
canary.flagger.app/hello   Initialized   0        2022-02-24T23:30:07Z

 

deployment hello-v1-primary 이 생성되고 hello-v1-canary, hello-v1-primary 가 자동 생성된다. 그리고 기존 hello-v1 deployment는 replicas=0 으로 변경된다.

 

여기서 hello-v1-primary 는 version1 (현재 운영중인 버전) 이 되고, hello-v1이 version2 ( 새로운 앱 )이라고 생각하면 된다.

 

브라우저에서 호출해 보면

 

아직 version1 이 호출된다. 이제 새로운 앱을 배포해 보자. 기존 hello-v1 deployment의 이미지를 변경해 보겠다.

 

kubectl -n test set image deployment/hello-v1 hello=ghcr.io/kmaster8/hello:v2

 

이미지 변경은 하면 아래와 같이 hello-v1 이 생성되는 것을 볼 수 있다. flagger의 상태도 Progressing으로 변경된다.

 # kubectl get all -n test
NAME                                    READY   STATUS    RESTARTS   AGE
pod/hello-v1-b97758d4b-k2rtg            1/1     Running   0          7s
pod/hello-v1-primary-7479df5558-49scr   1/1     Running   0          3m57s
pod/hello-v1-primary-7479df5558-blrkr   1/1     Running   0          3m57s
pod/hello-v1-primary-7479df5558-zm47x   1/1     Running   0          3m57s

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                        AGE
service/hello-v1           ClusterIP   10.103.254.249   <none>        5000/TCP                                       5m21s
service/hello-v1-canary    ClusterIP   10.107.16.18     <none>        5000/TCP                                       3m57s
service/hello-v1-primary   ClusterIP   10.106.97.212    <none>        5000/TCP                                       3m57s

NAME                                READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/hello-v1            1/1     1            1           4m15s
deployment.apps/hello-v1-primary    3/3     3            3           3m57s

NAME                                           DESIRED   CURRENT   READY   AGE
replicaset.apps/hello-v1-55575b9897            0         0         0       4m15s
replicaset.apps/hello-v1-b97758d4b             1         1         1       8s
replicaset.apps/hello-v1-primary-7479df5558    3         3         3       3m57s

NAME                       STATUS        WEIGHT   LASTTRANSITIONTIME
canary.flagger.app/hello   Progressing   0        2022-02-24T23:33:47Z

 

ingress를 보면

 

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    kubernetes.io/ingress.class: nginx
    kustomize.toolkit.fluxcd.io/reconcile: disabled
    nginx.ingress.kubernetes.io/canary: "true"
    nginx.ingress.kubernetes.io/canary-weight: "0"
  creationTimestamp: "2022-02-24T23:48:37Z"
  generation: 1
  name: prod-ingress-canary
  namespace: test
  ownerReferences:
  - apiVersion: flagger.app/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: Canary
    name: hello
    uid: 434d4569-1e6c-4034-a5f8-4d56485c4a9a
  resourceVersion: "10329251"
  selfLink: /apis/networking.k8s.io/v1/namespaces/test/ingresses/prod-ingress-canary
  uid: 8494232d-b799-45b7-9abd-48fbe4b66e23
spec:
  rules:
  - host: test.prod.10.60.200.121.sslip.io
    http:
      paths:
      - backend:
          service:
            name: hello-v1-canary
            port:
              number: 5000
        path: /
        pathType: Prefix
status:
  loadBalancer:
    ingress:
    - ip: 10.109.32.60

 

위와 같이 nginx.ingress.kubernetes.io/canary-weight: "0" 설정이 들어가는 것을 볼 수 있다.  현재는 canary배포가 아닌 blue/green 배포라서 0이 세팅되고 canary 배포로 설정하면 비율값이 들어가도록 되어 있다.

 

# kubectl describe canary -n test hello
...
Events:
  Type     Reason  Age                    From     Message
  ----     ------  ----                   ----     -------
  Warning  Synced  5m18s                  flagger  hello-v1-primary.test not ready: waiting for rollout to finish: observed deployment generation less than desired generation
  Normal   Synced  5m8s (x2 over 5m18s)   flagger  all the metrics providers are available!
  Normal   Synced  5m8s                   flagger  Initialization done! hello.test
  Normal   Synced  69s (x2 over 5m49s)    flagger  New revision detected! Scaling up hello-v1.test
  Normal   Synced  49s                    flagger  New revision detected! Restarting analysis for hello-v1.test
  Normal   Synced  39s (x3 over 5m39s)    flagger  Starting canary analysis for hello-v1.test
  Normal   Synced  39s (x3 over 5m39s)    flagger  Advance hello.test canary iteration 1/3
  Normal   Synced  29s                    flagger  Advance hello.test canary iteration 2/3
  Normal   Synced  19s                    flagger  Advance hello.test canary iteration 3/3
  Normal   Synced  9s                     flagger  Routing all traffic to canary

---

 

현재 blue/green 검증을 위한 error-rate metric을 조회하여 에러율이 0이면 자동으로 신규앱으로 이동함을 확인할 수 있다.

 

 

결론

보통 Blue/Green, Canary, A/B 테스트 등은 모두 배포 후 별도 검증을 통해 이상이 없으면 운영자가 routing rule 을 변경하여 배포하는 방식이다.

 

여기서 별도 검증이라고 하면 에러율 ( 404, 500 등 ) 이 없는 조건이거나, 응답시간이 ~초 이하 등이 될 수 있을 것이다.

 

Flagger 을 사용하면 사용자의 요청에 대한 에러율, 응답시간 등 다양한 조건을 Prometheus query로 만들어서 운영자가 개입하지 않고서도 자동 배포를 만들 수 있는 유용한 도구이다.

 

 

반응형
Comments