ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Kubernetes] Canary 배포 환경 구성
    개발/Infra 2022. 12. 30. 11:09

    0. 개요

    • Canary 배포란?
      • 구 버전의 서비스와 새 버전의 서비스를 동시 구성, 일부 트래픽을 새 버전으로 분산하여 에러 여부 판단 및 모니터링 하는 배포방식
      • 보통 구 버전은 PRODUCTION / 새 버전은 CANARY 로 서비스를 칭한다.

    그림 출처: https://medium.com/@domi.stoehr/canary-deployments-on-kubernetes-without-service-mesh-425b7e4cc862

    • k8s의 nginx-ingress-controller를 통해 트래픽을 분산할 수 있다.
      • weight 기반: 100 - X % / X % 로 트래픽 분산 (random)
      • cookie 기반: 요청의 cookie value를 확인하고 production / canary 서비스로 분산
      • header 기반: 요청의 header value를 확인하고 production / canary 서비스로 분산

     

    1. 테스트용 프로젝트 생성

    • 간단하게 2개의 프로젝트를 구성한다.
    • 동일한 API path를 가졌지만, 구 버전과 새 버전의 응답값이 다르게 (production / canary 구분 가능하도록) 구성했다. 
    @RestController
    public class ProductionController {
    	
        @GetMapping("/test")
        public ResponseEntity<String> test() {
        	return new ResponseEntity<String>("[PRODUCTION] requested.", HttpStatus.OK);
        }
    }
    @RestController
    public class CanaryController {
    	
        @GetMapping("/test")
        public ResponseEntity<String> test() {
        	return new ResponseEntity<String>("[CANARY] requested.", HttpStatus.OK);
        }
    }

     

    2. Docker build

    • 해당 서비스를 k8s에 올리기 위해 Docker image를 만든다.
    • Dockerfile도 간단하게 만든다..!
    FROM adoptopenjdk/openjdk11
    ARG JAR_FILE_PATH=./build/libs/TestProject-0.0.1-SNAPSHOT.jar
    COPY ${JAR_FILE_PATH} TestProject-0.0.1-SNAPSHOT.jar
    ENTRYPOINT ["java","-jar","TestProject-0.0.1-SNAPSHOT.jar"]
    • 해당 도커파일로 이미지를 빌드한다.
    • 나는 구 버전을 test-project:0.0.1, 새 버전을 test-project-0.0.2 로 빌드했다.
    $ docker build -t test-project:0.0.1 . 
    ...
    $ docker build -t test-project:0.0.2 .
    ...
    $ docker images
    REPOSITORY		TAG		IMAGE ID	CREATED		SIZE
    test-project		0.0.1		...		1 minute ago	459MB
    test-project		0.0.2		...		1 minute ago	459MB

     

    3. Kubernetes 배포

    • 구 버전 / 새 버전의 서비스 총 2개의 서비스를 Kubernetes에 올리기 위해 yaml 파일을 만든다. (커맨드로도 가능하다.)
    • 아래 예시는 k8s deployment 생성과 service 노출(등록) 설정이다. production / canary 서비스 각각 이미지가 다르다.

     

    • production-service.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: svc-production
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: svc-production
      template:
        metadata:
          labels:
            app: svc-production
        spec:
          containers:
            - name: svc-production
              image: test-project:0.0.1
              ports:
               - containerPort: 8080
    ---
    apiVersion: v1
    kind: Service
    metadata:
    name: svc-production
    spec:
      type: NodePort
      selector:
        app: svc-production
      ports:
        - port: 8080
    • canary-service.yaml
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: svc-canary
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: svc-canary
      template:
        metadata:
          labels:
            app: svc-canary
        spec:
          containers:
            - name: svc-canary
              image: test-project:0.0.2
              ports:
               - containerPort: 8080
    ---
    apiVersion: v1
    kind: Service
    metadata:
    name: svc-canary
    spec:
      type: NodePort
      selector:
        app: svc-canary
      ports:
        - port: 8080

     

    • 이제 위 2개의 파일을 가지고 서비스를 만들어보자
    $ kubectl apply -f production-service.yaml
    deployment.apps/svc-production created
    service/svc-production created
    
    $ kubectl apply -f canary-service.yaml
    deployment.apps/svc-canary created
    service/svc-canary created
    • 서비스 확인 (deployment, service, pod, replicaset 모두 정상적으로 생성되었다.)
    $ kubectl get all
    NAME			READY	STATUS		RESTARTS	AGE
    pod/svc-canary-...	1/1	Running		0		1m
    pod/svc-production-...	1/1	Running		0		1m	
    
    NAME				TYPE		CLUSTER_IP		EXTERNAL-IP	PORT(S)		AGE
    service/svc-canary-...		NodePort	10.xxx.xxx.xxx		<none>		8080:32309/TCP	1m
    service/svc-production-...	NodePort	10.xxx.xxx.xxx		<none>		8080:31373/TCP	1m
    
    NAME					READY	UP-TO-DATE	AVAILABLE	AGE
    deployment.apps/svc-canary-...		1/1	1		1		1m
    deployment.apps/svc-production-...	1/1	1		1		1m
    
    NAME					DESIRED	CURRENT		READY		AGE
    replicaset.apps/svc-canary-...		1	1		1		1m
    replicaset.apps/svc-production-...	1	1		1		1m

     

    • 이제 해당 두 서비스를 클러스터 외부에서 접근하기 위해 ingress를 생성한다.
    • Ingress도 서비스와 마찬가지로 각각 yaml 파일로 생성한다.

     

    • production-ingress.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: production-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
    spec:
      rules:
        - host: canary-test.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: svc-production
                    port:
                      number: 8080
    • canary-ingress.yaml 파일을 생성하기 전에! production ingress 부터 생성해보자
    $ kubectl apply -f production-ingress.yaml
    ingress.networking.k8s.io/production-ingress created
    ...
    $ kubectl get ingress
    NAME			CLASS	HOSTS			ADDRESS			PORTS	AGE
    production-ingress	nginx	canary-test.com		192.xxx.xxx.xxx		80	1m
    • ingress를 생성한 후 kubectl get ingress 로 ingress 목록을 확인 할 때, ADDRESS가 아직 매핑이 안되있을 수 있다. (최대 n분 이상 소요 될 수 있다고 하니 기다려보자.)
    • ADDRESS가 매핑이 완료되었다면 production 서비스의 API를 호출해본다.
    $ curl http://canary-test.com/test
    [PRODUCTION] requested.
    • production 서버의 API 요청/응답이 정상적으로 이루어졌다.
    • 만약 정상 응답이 되지 않는다면 리눅스의 hosts 파일에 해당 host를 등록해야한다.
    $ vi /etc/hosts
    
    // 맨 아랫줄에 ip 호스트 등록
    ...
    127.0.0.1	localhost
    ...
    192.xxx.xxx.xxx canary-test.com

     

    4. Canary 배포 설정

    • 앞에서 production 서비스를 배포하고 ingress 도 설정을 완료했으니 이제 canary 서비스의 ingress 생성 및 설정을 해야한다.
    • 그 전에 nginx-ingress의 canary 관련 annotations를 간단히 살펴보자면..
    annotations type description
    nginx.ingress.kubernetes.io/canary String 카나리 배포 적용 여부("true", "false")
    nginx.ingress.kubernetes.io/canary-by-header String header key 값
    nginx.ingress.kubernetes.io/canary-by-header-value String header value 값
    nginx.ingress.kubernetes.io/canary-by-header-pattern String 정규표현식을 사용한 header value 확인
    nginx.ingress.kubernetes.io/canary-by-cookie String cookie 기반 라우팅 설정
    nginx.ingress.kubernetes.io/canary-weight String weight 기반 라우팅
    nginx.ingress.kubernetes.io/canary-weight-total String 총 weight 수. (default: 100)
    canary-weight-total: 1000
    canary-weight: 10 일 때,
    1000번 요청 중 10번 요청이 canary 서비스로 요청됨

    출처: https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#canary

     

    • canary-ingress.yaml
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: canary-ingress
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
        nginx.ingress.kubernetes.io/canary: "true" # enable canary
        # weight 기반 라우팅 (100번 요청 중 50번)
        nginx.ingress.kubernetes.io/canary-weight: "50"
        # cookie 기반 라우팅 (cookie에 canary=always 로 설정시 canary 서버로 요청, canary=never 일 때 production)
        nginx.ingress.kubernetes.io/canary-by-cookie: "canary" 
        # header 기반 라우팅 (header에 target=canary 로 설정시 canary 서버로 요청, 아니면 production)
        nginx.ingress.kubernetes.io/canary-by-header: "target"
        nginx.ingress.kubernetes.io/canary-by-header-value: "canary"
    spec:
      rules:
        - host: canary-test.com
          http:
            paths:
              - path: /
                pathType: Prefix
                backend:
                  service:
                    name: svc-canary
                    port:
                      number: 8080
    • 생성 및 적용
    $ kubectl apply -f production-ingress.yaml
    ingress.networking.k8s.io/production-ingress created
    ...
    $ kubectl get ingress
    NAME			CLASS	HOSTS			ADDRESS			PORTS	AGE
    production-ingress	nginx	canary-test.com		192.xxx.xxx.xxx		80	10m
    canary-ingress		nginx	canary-test.com		192.xxx.xxx.xxx		80	1m
    • http://canary-test.com의 동일한 호스트로 production / canary 서비스가 canary 배포 구성이 완료되었다.

     

    5. Test

    5-1. weight 기반 라우팅

    $ curl http://canary-test.com/test
    [PRODUCTION] requested.
    $ curl http://canary-test.com/test
    [CANARY] requested.
    $ curl http://canary-test.com/test
    [PRODUCTION] requested.
    $ curl http://canary-test.com/test
    [PRODUCTION] requested.
    $ curl http://canary-test.com/test
    [CANARY] requested.

    5-2. cookie 기반 라우팅

    $ curl --cookie "canary=always" canary-test.com/test
    [CANARY] requested.
    $ curl --cookie "canary=always" canary-test.com/test
    [CANARY] requested.
    $ curl --cookie "canary=always" canary-test.com/test
    [CANARY] requested.
    $ curl --cookie "canary=always" canary-test.com/test
    [CANARY] requested.
    $ curl --cookie "canary=never" canary-test.com/test
    [PRODUCTION] requested.
    $ curl --cookie "canary=never" canary-test.com/test
    [PRODUCTION] requested.
    $ curl --cookie "canary=never" canary-test.com/test
    [PRODUCTION] requested.
    $ curl --cookie "canary=never" canary-test.com/test
    [PRODUCTION] requested.

    5-3. header 기반 라우팅

    $ curl -H "target:canary" canary-test.com/test
    [CANARY] requested.
    $ curl -H "target:canary" canary-test.com/test
    [CANARY] requested.
    $ curl -H "target:canary" canary-test.com/test
    [CANARY] requested.
    $ curl -H "target:canary" canary-test.com/test
    [CANARY] requested.
    $ curl -H "target:canary" canary-test.com/test
    [CANARY] requested.

    '개발 > Infra' 카테고리의 다른 글

    어떤 캐싱을 도입할까? feat. 로컬캐시, Redis, 웹캐시  (0) 2023.01.07
Designed by Tistory.