무중단 배포란?
이전에 컨트롤러 포스팅에서 Deployment Controller를 이용한 배포에 관해 잠시 설명을 했었습니다. 이번 포스팅에서는 쿠버네티스의 다양한 배포 방식과 그리고 무중단 배포에 관해서 다뤄보도록 하겠습니다.
먼저 무중단 배포가 무엇인지부터 알아보겠습니다. 무중단 배포란 서버를 실제로 서비스할 때 서비스적인 장애와 배포에 있어서 부담감을 최소화하고, 서비스가 중단되지 않도록 배포하는 기술입니다.
예를 들어, 동시 접속자 수가 만명 이상인 A라는 성공적인 e-commerce회사가 있다고 해보겠습니다. A라는 회사에는 동시에 접속해서 물건을 상품을 구매 및 등록하는 유저가 많기 때문에 지속적으로 장애 없이 서비스를 유지해야 할 필요성이 있습니다. 만약 중간에 1분이라도 장애가 서비스가 중지된다면 회사 입장에서 큰 금전적인 손실을 입게 되기 때문입니다.
그런데 이번에 새로운 기능 추가하기 위해서 A라는 회사는 배포를 해야하는 상황에 놓이게 되었습니다. 단순히 배포를 하게 되면 배포를 하는 그 순간 동안은 서버가 트래픽을 받을 수 없게 되기 때문에 어쩔 수 없이 서비스가 중단되게 됩니다. 그래서 차선의 방법으로 A회사 배포팀은 사람들이 가장 덜 서비스를 사용할 것 같은 (서비스에 가장 부담감이 적을 것 같은) 목요일 새벽 4시에 배포를 하기로 결심합니다. 성공적으로 배포에 성공한다면 서비스 중지 시간을 최소화할 수 있을 것입니다. 하지만 예상치 못한 버그로 배포를 하던 중 장애가 발생하게 되었고, 다음 날 아침까지 서비스가 중단되는 일이 발생하게 됩니다.
위의 예시는 조금 최악의 상황을 가정하여 들은 예시지만, 충분히 있을 법한 예시입니다. 실제로 이전에는 이와 같이 배포 자체가 하나의 거대한 일이었고, 이를 위해 따로 편성된 팀이 존재했으며 새벽에 배포하는 일이 잦았습니다. 하지만 최근 무중단 배포 기술을 가진 deploy 자동화 툴들이 개발되며 개발자들이 배포와 운영까지 담당하는 DevOps의 역할을 하게 되었습니다. 그렇다면 무중단 배포란 무엇인지 다시 풀어 설명해보도록 하겠습니다.
무중단 배포란 서비스하는 웹 애플리케이션을 내리지 않으며 새로운 웹 애플리케이션을 올린 후 만약 그 서버가 정상적으로 작동한다면, 그제서야 트래픽을 새로운 버전의 애플리케이션으로 보내는 것입니다. 그리하여 궁극적으로 서비스가 중단되지 않으며 배포할 수 있도록 하는 기술입니다.
Rolling Update
쿠버네티스에서는 이와 같은 무중단 배포를 지원합니다. 대표적인 3가지 방법으로 블루/그린 방법, 롤링 업데이트 방법, 카나리 배포 방법이 있습니다. 그중 이번 포스팅에서는 가장 많이 사용되는 배포 방식 중 하나인 Rolling Update에 대해서 알아보겠습니다. 롤링 업데이트란 새 버전을 배포하면서, 새 버전 인스턴스를 하나씩 늘려가고 기존 버전의 인스턴스를 하나식 줄여나가는 방식입니다. 이러한 경우 새 버전의 인스턴스로 트래픽이 이전되기 전까지 이전 버전과 새 버전의 인스턴스가 동시에 존재할 수 있다는 단점이 있지만, 시스템을 무중단으로 업데이트 할 수 있다는 장점이 있습니다.
쿠버네티스에서 Replica Set(이하 RS)을 이용하는 방법은 아래와 같습니다.
- 먼저 예시의 상황은 RS가 v1 버전의 파드들을 관리하고 3개의 파드가 서비스되고 있는 상황입니다.
- 이때, v2버전의 pod3개를 배포해야하는 상황이 생깁니다. 그래서 2 파드를 컨트롤할 RS를 만들고 replica를 1로 해서 v2 Pod를 하나 생성합니다. 그리고 RS v1에서는 replica의 수를 3에서 2로 줄이고 v1 파드의 수를 2개로 조정하게 됩니다.
- 같은 방식으로 v1의 파드는 수를 줄여가고 v2의 파드는 수를 늘리게 됩니다. 그리고 최종적으로 v1의 파드는 0개가 되고 RC v1은 삭제됩니다.
이와 같이 롤링 업데이트를 하기 위해서는 RS가 두개 필요하고, replica의 수를 단계적으로 조절해주어야 합니다. 또한 배포가 잘못되었을 경우 롤백을 하기 위해 순서를 거꾸로 실행해야 하는 등 불편함이 있습니다.
Deployment
그래서 레플리카셋만을 이용해서 롤링 업데이트를 구현할 수도 있지만, 운영이 복잡해지는 단점이 있습니다. 그래서 쿠버네티스에서는 RC를 직접적으로 이용하지 않고 Deployment라는 RC를 이용한 롤링 업데이트 과정을 자동화해주는 추상화 개념을 사용합니다.
그렇다면 Deployment를 이용한 템플릿 필드에 대해 좀 더 자세히 살펴보도록 하겠습니다.
.spec.strategy.type에는 두 가지 가능한 필드가 존재합니다.
- RollingUpdate : 새로운 파드가 점진적으로 추가되고 이전 파드가 중지됩니다.
- Recreate : 새로운 파드가 추가되기 전에 모든 이전 파드가 한 번에 중지됩니다.
앞서 설명 들였듯 Rolling Update가 배포에 가장 적합한 업데이트 전략으로 현재 Deployment의 디폴트 배포 전략입니다. RollingUpdate에는 업데이트 프로세스를 미세하게 조정할 수 있는 두 가지 옵션이 더 존재하는데 아래와 같습니다.
- maxSurge : 업데이트 중에 동시에 최대로 추가할 수 있는 파드의 수입니다. (새로운 버전의 파드를 미리 생성해야 하므로)
- maxUnavailable : 업데이트 과정 중에서 동시에 사용 불가능한 상태가 될 수 있는 파드의 수 입니다.
maxSurge와 maxUnavailable 둘 다 정수나 % 단위로 지정할 수 있지만, 둘다 0이 될 수는 없습니다. (둘 다 0이 되면 배포할 수 없으므로) 값을 정수로 지정하게 될 경우 실제로 업데이트되는 파드의 수가 지정됩니다. 그리고 백분율(%)로 지정하게 되면 원하는 개수의 파드의 비율이 반올림되어 지정됩니다.
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
Pod Affinity / Anti Affinity
affinity와 anti-affinity는 deployment나 stateful set으로 파드를 배포할 때 개별 파드의 관계를 정의하는 용도로 사용됩니다. affinity나 anti-affinity를 사용할 때 각각 다른 환경에서 두 가지 옵션을 사용할 수 있습니다.
- requiredDuringSchedulingIgnoredDuringExcution : 일치하는 노드가 없어서 선호도 구성과 일치하지 않으면 파드를 노드에 스케줄링할 수 없습니다.
- preferredDuringSchedulingIgnoredDuringException : 스케줄러가 선호도 구성과 일치하는 노드에서 파드를 스케줄 하려고 시도하지만, 이를 수행할 수 없는 경우 다른 노드에서 스케줄 됩니다.
affinity
affinity는 특정 노드에 파드를 스케줄링할 때 사용됩니다. 특정 파드를 GPU가 있는 노드에서 실행시키거나, 또는 다른 파드와 함께 배치함으로써 효과를 볼 수 있는 경우 유용하게 사용할 수 있습니다. 실제로 서비스를 운영하다 보면 특별한 몇몇 파드 끼리는 통신이 긴밀하게 이루어지는 경우가 있습니다. 예를 들면 데이터베이스와 캐시 같은 서비스와 통신하는 앱 컨테이너들이 있습니다. 이러한 컨테이너들을 같은 노드에 두어서 네트워크 통신 비용을 줄일 수 있습니다.
Kubernetes Cluster를 구성할 때 리눅스의 BPF(리눅스 커널에 포함될 기술로 실행 중인 상태에서 리눅스를 재시작하지 않고 커널에 코드를 삽입할 수 있도록 함)와 XD(실행 중인 리눅스의 패킷 경로를 재시작 없이 조정할 수 있도록 합니다. -> 패킷 성능이 올라감)를 이용하는 실리엄 네트워크 플러그인(https://cilium.io/)을 이용하면 같은 노드에 속한 컨테이너끼리 내부 통신 성능이 더욱 좋아집니다.
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- cache
topologyKey: "kubernetes.io/hostname"
anti-affinity
anti-affinity는 affinity와 반대로 서로 같이 있기를 원하지 않는 파드를 지정해주어 다른 노드에 띄워주는 용도로 사용됩니다. 예를 들면 CPU나 네트워크 같은 하드웨어 자원을 많이 사용하는 앱 컨테이너는 여러 노드로 분산시켜주는 것이 좋습니다. 만약 anti-affinity를 설정하지 않은 상태로 추가적으로 cale-out을 하게 될 경우, 이미 시스템 자원을 많이 사용하고 있는 노드에 추가적으로 파드를 띄우게 되는 경우가 있습니다. 그렇게 되면 오히려 성능이 떨어지게 되는 문제가 발생하게 됩니다.
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
affinity와 anti-affinit를 구성할 때 가장 중요한 점은 이와 같이 파드를 특정 노드에 배치하도록 정의할 수는 있지만 실제로 파드가 스케줄링될 때 모든 상황에서 절대적이지는 않다는 점입니다. (클러스터 전체의 환경이 지정한 옵션의 상황과 다를 수 있기 때문에) 그렇기 때문에 특정 상황에서는 매우 유용한 기능이 될 수 있지만, 파드가 실행되는 노드의 위치를 반드시 제어해야 한다면 해당 기능은 큰 도움이 되지 않을 수도 있으므로 kubernetes scheduler를 참조하는 것이 좋습니다.
※ affinity와 anti-affinity에 대한 kubernetes 전체 설명서
'DevOps > Kubernetes' 카테고리의 다른 글
[Kubernetes] ConfigMap / Secret: config 정보를 container 외부에서 가져오기 (0) | 2020.07.22 |
---|---|
[Kubernetes] Volume: k8s에서 데이터를 Disk에 영구 저장하기 (0) | 2020.07.20 |
[Kubernetes] Service / Ingress: L4 L7 Load Balancer (0) | 2020.07.16 |
[Kubernetes] 쿠버네티스 컨트롤러: Pod를 동적으로 관리 (0) | 2020.07.13 |
[Kubernetes] Pod #2: 쿠버네티스 셀프 힐링 및 자원 할당 (0) | 2020.07.13 |