점진적 배포
점진적 배포 (Progressive Delivery)
Linkerd의 동적 요청 라우팅을 사용하면 서비스 간에 트래픽을 동적으로 이동시킬 수 있어요. 이 기능을 활용해 블루-그린 배포나 카나리 배포 같은 리스크가 낮은 배포 전략을 구현할 수 있어요.
하지만 단순히 트래픽을 서비스의 한 버전에서 다음 버전으로 이동하는 것은 시작에 불과해요. 트래픽 분할을 Linkerd의 자동 골든 메트릭 텔레메트리와 결합하면, 관찰된 메트릭을 기반으로 트래픽 결정을 내릴 수 있어요. 예를 들어, 성공률을 계속 모니터링하면서 새 디플로이먼트로 트래픽을 점진적으로 옮길 수 있어요. 어떤 시점에서든 성공률이 떨어지면 원래 디플로이먼트로 트래픽을 되돌리고 릴리스를 중단할 수 있어요. 이상적으로는 사용자들이 내내 행복하게 아무것도 느끼지 못해야 해요!
이 튜토리얼에서는 두 가지 다른 점진적 배포 도구, Flagger와 Argo Rollouts를 사용하는 방법과, Linkerd의 메트릭과 요청 라우팅을 제어 루프로 묶어 완전히 자동화된 메트릭 인지 카나리 배포를 만드는 방법을 보여줄게요.
본문
Linkerd 운영 팁
이 페이지에는 오픈 소스 커뮤니티가 최선의 노력(best-effort)으로 작성한 설명이 포함되어 있어요. 미션 크리티컬 애플리케이션을 운영하는 사용자는 Linkerd 프로덕션 리소스를 숙지하거나 상용 Linkerd 제공업체와 연결해야 해요.
사전 요구 사항
이 가이드를 사용하려면 다음이 실행 중인 Kubernetes 클러스터가 필요해요:
- Linkerd와 Linkerd-Viz. 아직 설치하지 않았다면 Installing Linkerd 가이드를 따르세요.
Flagger
Flagger 설치
Linkerd가 실제 트래픽 라우팅을 관리하는 동안, Flagger는 새 Kubernetes 리소스 생성, 메트릭 관찰, 새 버전으로 사용자를 점진적으로 보내는 과정을 자동화해요. Flagger를 클러스터에 추가하고 Linkerd와 함께 동작하도록 구성하려면 다음을 실행해요:
kubectl apply -k github.com/fluxcd/flagger/kustomize/linkerd
이 명령은 다음을 추가해요:
- 롤아웃이 어떻게 진행되어야 하는지 구성할 수 있게 해주는 canary CRD.
- 디플로이먼트와 서비스처럼 Flagger가 수정해야 하는 모든 리소스를 변경할 권한을 부여하는 RBAC.
- Linkerd 컨트롤 플레인과 상호작용하도록 구성된 Flagger 컨트롤러.
모든 것이 실행될 때까지 kubectl로 확인할 수 있어요:
kubectl -n flagger-system rollout status deploy/flagger
데모 설정
이 데모는 로드 생성기, 디플로이먼트, 프론트엔드의 세 가지 컴포넌트로 구성돼요. 디플로이먼트는 이름 같은 정보를 반환하는 파드를 만들고, 그 응답을 사용해 Flagger가 조율하는 점진적 롤아웃을 관찰할 수 있어요. 로드 생성기는 적극적인 트래픽이 있어야 작업을 완료할 수 있으므로 롤아웃 실행을 더 쉽게 만들어줘요. 이 컴포넌트들은 함께 다음과 같은 토폴로지를 가져요:
Topology
이 컴포넌트들을 클러스터에 추가하고 Linkerd 데이터 플레인에 포함시키려면 다음을 실행해요:
kubectl create ns test && \
kubectl apply -f https://run.linkerd.io/flagger.yml
모든 것이 성공적으로 시작됐는지 다음으로 확인해요:
kubectl -n test rollout status deploy podinfo
frontend 서비스를 로컬로 포워딩하고 로컬에서 http://localhost:8080을 열어 확인해보세요:
kubectl -n test port-forward svc/frontend 8080
참고
요청 라우팅은 연결의 서버 측이 아니라 클라이언트 측에서 발생해요. 메시 밖에서 오는 모든 요청은 이동되지 않으며 항상 기본 백엔드로 향해요. 소스가 메시의 일부가 아니므로 LoadBalancer 유형의 서비스는 이 동작을 나타내요. 외부 트래픽을 이동하려면 인그레스 컨트롤러를 메시에 추가하세요.
릴리스 구성
무언가를 변경하기 전에, 클러스터에서 릴리스가 어떻게 롤아웃되어야 하는지 구성해야 해요. 구성은 Canary와 MetricTemplate 정의에 담겨 있어요. 클러스터에 적용하려면 다음을 실행해요:
kubectl apply -f - apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
name: podinfo
namespace: test
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: podinfo
service:
# service port number
port: 9898
# container port number or name (optional)
targetPort: 9898
# Reference to the Service that the generated HTTPRoute would attach to.
gatewayRefs:
- name: podinfo
namespace: test
group: core
kind: Service
port: 9898
analysis:
interval: 10s
threshold: 5
stepWeight: 10
maxWeight: 100
metrics:
- name: success-rate
templateRef:
name: success-rate
namespace: test
thresholdRange:
min: 99
interval: 1m
---
apiVersion: flagger.app/v1beta1
kind: MetricTemplate
metadata:
name: success-rate
namespace: test
spec:
provider:
type: prometheus
address: http://prometheus.linkerd-viz:9090
query: |
sum(
rate(
response_total{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
classification!="failure",
direction="inbound"
}[{{ interval }}]
)
)
/
sum(
rate(
response_total{
namespace="{{ namespace }}",
deployment=~"{{ target }}",
direction="inbound"
}[{{ interval }}]
)
)
* 100
EOF
Flagger 컨트롤러는 이 정의들을 감시하고 클러스터에 몇 가지 새 리소스를 만들 거예요. 이를 보려면 다음을 실행해요:
kubectl -n test get ev --watch
podinfo와 동일한 수의 복제본을 가진 podinfo-primary라는 새 디플로이먼트가 생성돼요. 새 파드가 준비되면 원래 디플로이먼트는 0으로 스케일 다운돼요. 이는 Flagger가 구현 세부 사항으로 관리하는 디플로이먼트를 제공하면서, 원래 구성 파일과 워크플로우를 유지해줘요. 다음 줄이 보이면 모든 것이 설정된 거예요:
0s Normal Synced canary/podinfo Initialization done! podinfo.test
관리되는 디플로이먼트 외에도, 애플리케이션의 새 버전과 이전 버전 사이에 트래픽을 라우팅하도록 조율하는 서비스도 생성돼요. 이들은 kubectl -n test get svc로 확인할 수 있으며, 다음과 같을 거예요:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
frontend ClusterIP 10.7.251.33 8080/TCP 96m
podinfo ClusterIP 10.7.252.86 9898/TCP 96m
podinfo-canary ClusterIP 10.7.245.17 9898/TCP 23m
podinfo-primary ClusterIP 10.7.249.63 9898/TCP 23m
이 시점에서 토폴로지는 대략 다음과 같아요:
Initialized
참고
이 가이드는 Flagger가 제공하는 모든 기능을 거의 다루지 않아요. 카나리 릴리스를 HPA와 결합하거나, 커스텀 메트릭으로 작업하거나, A/B 테스트 같은 다른 유형의 릴리스를 하는 데 관심이 있다면 문서를 꼭 읽어보세요.
롤아웃 시작
시스템으로서 Kubernetes 리소스에는 spec과 status라는 두 가지 주요 섹션이 있어요. 컨트롤러는 spec을 보면 현재 시스템의 status를 spec과 일치시키기 위해 최선을 다해요. 디플로이먼트에서 파드 spec 구성 중 하나라도 변경되면 컨트롤러가 롤아웃을 시작해요. 기본적으로 디플로이먼트 컨트롤러는 rolling update를 조율해요.
이 예시에서 Flagger는 디플로이먼트의 spec이 변경된 것을 감지하고 카나리 롤아웃을 조율하기 시작해요. 이 과정을 시작하려면 이미지를 새 버전으로 업데이트할 수 있어요:
kubectl -n test set image deployment/podinfo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
환경 변수나 어노테이션 업데이트 같은 파드 spec에 대한 어떤 종류의 수정이든 이미지 업데이트와 동일한 동작을 일으켜요.
업데이트 시 카나리 디플로이먼트(podinfo)가 스케일 업돼요. 준비되면 Flagger는 HTTPRoute를 점진적으로 업데이트하기 시작해요. 구성된 stepWeight가 10이므로, 각 증가분마다 podinfo의 가중치가 10씩 늘어나요. 각 기간마다 성공률이 관찰되고, 99% 임계값을 넘는 한 Flagger는 롤아웃을 계속해요. 이 전체 과정을 보려면 다음을 실행해요:
kubectl -n test get ev --watch
업데이트가 진행되는 동안 리소스와 트래픽은 대략 다음과 같이 보일 거예요:
Ongoing
업데이트가 완료되면 이 그림은 이전 섹션의 그림과 같아져요.
참고
이미지 태그를 1.7.1과 1.7.0 사이에서 전환해 롤아웃을 다시 시작할 수 있어요.
리소스
canary 리소스는 현재 상태와 진행 상황으로 업데이트돼요. 다음을 실행해 확인할 수 있어요:
watch kubectl -n test get canary
뒤에서 Flagger는 HTTPRoute 리소스를 업데이트해 primary와 canary 백엔드 사이에서 트래픽을 분할하고 있어요. 롤아웃이 진행되는 동안 이 구성이 어떻게 변하는지 보려면 다음을 실행해요:
kubectl -n test get httproute.gateway.networking.k8s.io podinfo -o yaml
각 증가분마다 podinfo-canary의 가중치가 늘어나고 podinfo-primary의 가중치가 줄어들어요. 롤아웃이 성공하면 podinfo-primary의 가중치는 100으로 돌아가고, 밑에 있는 카나리 디플로이먼트(podinfo)는 스케일 다운돼요.
메트릭
트래픽이 primary 디플로이먼트에서 canary 디플로이먼트로 이동함에 따라, Linkerd는 요청 대상에 무슨 일이 일어나고 있는지 가시성을 제공해요. 메트릭은 실시간으로 트래픽을 받는 백엔드를 보여주고 성공률, 지연 시간, 처리량을 측정해요. CLI에서 다음을 실행해 확인할 수 있어요:
watch linkerd viz -n test stat deploy --from deploy/load
브라우저
http://localhost:8080을 다시 방문해 보세요. 페이지를 새로고침하면 새 버전과 다른 헤더 색상이 전환되는 것을 볼 수 있어요. 또는 curl http://localhost:8080을 실행하면 다음과 같은 JSON 응답이 반환돼요:
{
"hostname": "podinfo-primary-74459c7db8-lbtxf",
"version": "1.7.0",
"revision": "4fc593f42c7cd2e7319c83f6bfd3743c05523883",
"color": "blue",
"message": "greetings from podinfo v1.7.0",
"goos": "linux",
"goarch": "amd64",
"runtime": "go1.11.2",
"num_goroutine": "6",
"num_cpu": "8"
}
이 응답은 롤아웃이 계속됨에 따라 서서히 변할 거예요.
정리
정리를 위해 Flagger 컨트롤러를 클러스터에서 제거하고 test 네임스페이스를 삭제해요:
kubectl delete -k github.com/fluxcd/flagger/kustomize/linkerd && \
kubectl delete ns test
Argo Rollouts
Argo Rollouts는 트래픽 메트릭을 기반으로 점진적 카나리 롤아웃을 수행하기 위해 Linkerd를 사용할 수 있는 또 다른 도구예요.
Argo Rollouts 설치
Flagger와 유사하게, Argo Rollouts는 새 Kubernetes 리소스 생성, 메트릭 관찰 과정을 자동화하고 Linkerd를 사용해 새 버전으로 트래픽을 점진적으로 이동시켜요. Argo Rollouts를 설치하려면 다음을 실행해요:
kubectl create namespace argo-rollouts && \
kubectl apply -n argo-rollouts -f https://github.com/argoproj/argo-rollouts/releases/latest/download/install.yaml
Argo Rollouts를 Linkerd와 함께 사용하려면 GatewayAPI 라우팅 플러그인을 활성화하고 HTTPRoutes를 읽고 수정할 수 있는 필요한 RBAC를 부여해야 해요:
kubectl apply -f - apiVersion: v1
kind: ConfigMap
metadata:
name: argo-rollouts-config # must be so name
namespace: argo-rollouts # must be in this namespace
data:
trafficRouterPlugins: |-
- name: "argoproj-labs/gatewayAPI"
location: "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-gatewayapi/releases/download/v0.0.0-rc1/gateway-api-plugin-linux-amd64"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: argo-controller-role
namespace: argo-rollouts
rules:
- apiGroups:
- gateway.networking.k8s.io
resources:
- httproutes
verbs:
- "*"
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: argo-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: argo-controller-role
subjects:
- namespace: argo-rollouts
kind: ServiceAccount
name: argo-rollouts
EOF
마지막으로, 명령줄에서 롤아웃을 제어할 수 있도록 Kubectl용 Argo Rollouts 플러그인도 필요해요. 다음 설명을 따라 설치하세요.
데모 설정
Flagger를 시연할 때 사용한 것과 동일한 데모 애플리케이션을 사용할 수 있어요. 다음을 실행해 배포해요:
kubectl create ns test && \
kubectl apply -f https://run.linkerd.io/flagger.yml
롤아웃 구성
이 애플리케이션에 대한 롤아웃을 설정하려면 몇 가지 리소스를 만들 거예요: 안정(stable) 및 canary 버전용 서비스, 이 두 Service 사이의 라우팅을 제어하는 HTTPRoute, 그리고 롤아웃을 수행하는 방법을 구성하는 Rollout 리소스가 있어요:
kubectl apply -f - apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: argo-rollouts-http-route
namespace: test
spec:
parentRefs:
- name: podinfo
namespace: test
kind: Service
group: core
port: 9898
rules:
- backendRefs:
- name: podinfo-stable
namespace: test
port: 9898
- name: podinfo-canary
namespace: test
port: 9898
---
apiVersion: v1
kind: Service
metadata:
name: podinfo-canary
namespace: test
spec:
ports:
- port: 8989
targetPort: 8989
protocol: TCP
name: http
selector:
app: podinfo
---
apiVersion: v1
kind: Service
metadata:
name: podinfo-stable
namespace: test
spec:
ports:
- port: 8989
targetPort: 8989
protocol: TCP
name: http
selector:
app: podinfo
---
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: rollouts-demo
namespace: test
spec:
replicas: 1
strategy:
canary:
canaryService: podinfo-canary # our created canary service
stableService: podinfo-stable # our created stable service
trafficRouting:
plugins:
argoproj-labs/gatewayAPI:
httpRoute: argo-rollouts-http-route # our created httproute
namespace: test
steps:
- setWeight: 30
- pause: {}
- setWeight: 40
- pause: { duration: 10 }
- setWeight: 60
- pause: { duration: 10 }
- setWeight: 80
- pause: { duration: 10 }
revisionHistoryLimit: 2
selector:
matchLabels:
app: podinfo
template:
metadata:
labels:
app: podinfo
spec:
containers:
- name: podinfod
image: quay.io/stefanprodan/podinfo:1.7.0
ports:
- containerPort: 9898
protocol: TCP
EOF
롤아웃 시작
다음을 실행해 podinfo의 새 버전으로 롤아웃을 트리거할 수 있어요:
kubectl argo rollouts -n test set image rollouts-demo \
podinfod=quay.io/stefanprodan/podinfo:1.7.1
다음을 실행해 롤아웃 진행 상황을 관찰할 수 있어요:
kubectl argo rollouts -n test get rollout rollouts-demo --watch
뒤에서 Argo Rollouts는 HTTPRoute 리소스를 업데이트해 stable과 canary 백엔드 사이에서 트래픽을 분할하고 있어요. 롤아웃이 진행되는 동안 이 구성이 어떻게 변하는지 보려면 다음을 실행해요:
kubectl -n test get httproute.gateway.networking.k8s.io podinfo -o yaml
Linkerd CLI를 사용해 트래픽이 실시간으로 어떤 파드로 라우팅되는지 관찰할 수도 있어요:
watch linkerd viz -n test stat po --from deploy/load
정리
정리를 위해 클러스터에서 Argo Rollouts 컨트롤러를 제거하고 test 네임스페이스를 삭제해요:
kubectl delete ns argo-rollouts && \
kubectl delete ns test