싱글톤 서비스 패턴 은 동시에 하나의 어플리케이션 인스턴스만 활성하지만 고가용성을 보장합니다.
쿠버네티스에서는 플랫폼적으로 싱글톤 서비스 패턴 을 사용할 수도 있으며 어플리케이션 내에서 구현할 수 도 있습니다.
이번 글에서는 싱글톤 서비스 패턴 에대해서 설명하고 구현하는 방법을 얘기해보도록 하겠습니다.
어플리케이션을 만들 때 어떤 경우에는 동시에 하나의 서비스 인스턴스만 실행되어야할 때가 있습니다.
다음은 싱글톤 서비스 를 사용하는 예시입니다.
하지만 쿠버네티스의 주요 기능은 다중 인스턴스를 실행해서 시스템의 처리량과 가용성을 높이는 것입니다.
따라서 Deployment, ReplicaSet 과 같은 자원은 싱글톤 서비스 를 사용하는 데 어려움이 있습니다.
쿠버네티스에서 싱글톤 서비스 를 사용하기 위한 자원을 따로 정의한 것은 아니지만
어플리케이션 외부 잠금 과 내부 잠금 을 통해서 2가지 레벨로 구현할 수 있습니다.
이름에서 알 수 있듯이, 이 메커니즘은 어플리케이션 외부의 관리 프로세스를 사용하여
오직 어플리케이션 하나의 인스턴스만 실행하게 하는 방법입니다.
따라서 어플리케이션 자체에서는 이러한 제약사항을 인식하지 못하고 싱글톤 인스턴스로 실행됩니다.
쿠버네티스에서는 ReplicaSet 과 StatefulSet 을 통해서 구현할 수 있습니다.
Singleton Service 를 사용하는 가장 간단한 방법은 ReplicaSet 을 사용하는 것입니다.
쿠버네티스에서 ReplicaSet 은 replicas
에 정의한 수의 파드 개수를 보장해줍니다.
따라서 replicas
를 1
로 설정한다면 파드가 1개만 생성되기 때문에
Singleton Service 처럼 사용할 수 있게됩니다.
하지만 ReplicaSet은 기본적으로 고가용성 보장과 무중단을 목적으로 사용됩니다.
따라서 최소 1개를 보장할 뿐 1개 이상의 파드가 실행될 수 있습니다.
다음 예시의 경우 파드의 수가 1개 이상이 될 수 있습니다.
Singleton Service 는 회복과 복구가 가능하지만, 정의상으로 고가용성은 아닙니다.
또한 일반적으로 가용성보다는 일관성을 선호하기 때문에 StatefulSet 을 이용하는 것이 좋습니다.
StatefulSet 은 다음 장인 쿠버네티스 패턴 - 11장 스테이트풀 서비스 에서 자세하기 설명될 예정이기 때문에
여기서는 싱글톤에 적용되는 부분에 대해서만 간단히 설명하도록 하겠습니다.
2-1) 순서성
StatefulSet 은 순서가 있어 이전 파드가 이미 시작되지 않으면 해당 파드를 시작할 수 없습니다.
종료 프로세스도 마찬가지로 늦게 시작된 파드부터 종료되도록 합니다.
2-2) 일관된 볼륨
파드를 삭제할 때 파드와 연결된 볼륨이 자동으로 삭제되지 않습니다.
따라서 데이터를 삭제 시에 수동으로 제거해야하므로 관리 부담이 가중되지만
파드의 일관성 을 유지할 수 있습니다.
2-3) 안정적인 네트워크 식별자
Headless 서비스 를 사용하는 경우 DNS A Record 를 통해서 파드와 통신할 수 있습니다.
이 경우에는 DNS A Record 가 항상 유지되므로 파드가 재시작 되어도 같은 파드와 통신할 수 있습니다.
따라서 StatefulSet 을 사용하면 일관성이 유지되므로 엄격한 싱글톤 서비스 를 사용할 때 유용합니다.
분산 환경에서 서비스 인스턴스를 제어하는 한 가지 방법은 분산 락 을 사용하는 것 입니다.
서비스 인스턴스 또는 인스턴스 내부의 컴포넌트가 활성화 될 때마다
락을 획득하기위해 시도하며 성공하면 해당 서비스가 활성화됩니다.
락을 획득하지 못한 다음 순번의 서비스 인스턴스는 대기하며
현재 활성화된 서비스가 락을 해제할 대까지 지속적으로 락을 획득하기 위해 시도합니다.
이러한 전략은 싱글톤 서비스 를 만드는 데 활용할 수 있습니다.
이런 전략을 구현하는 클러스터 어플리케이션은 ZooKeeper, Redis, Commit, Etcd 등이 있습니다.
따라서 쿠버네티스의 마스터 노드에서 실행중인 Etcd 를 통해
어플리케이션 내부 잠금으로 싱글톤 서비스 를 사용한 예시를 알아보도록 하겠습니다.
먼저 Etcd 는 쿠버네티스가 상태를 유지하기 위해 사용하는 key-value Store 입니다.
또한 HA 를 위해 Store 를 복제하고 상태를 유지하기 위해 Raft 프로토콜을 사용합니다.
여기서 Etcd 는 리더 선출 구현을 위해 필요한 빌딩 블록을 제공하며
몇몇 클라이언트 라이브러리에는 이미 이 기능이 구현되어 있습니다.
특히 Apache Camel 의 경우에는 Etcd 의 lock 메커니즘을 사용할 수 있도록 커넥터를 제공합니다.
이 커넥터는 쿠버네티스 Optimistic locking guarantee 를 사용하여
동시에 하나의 파드만 수정이 가능한 컨피그맵 등의 자원을 편집합니다.
또한 Optimistic locking guarantee 를 통해서 하나의 인스턴스만 활성화되게 할 수 있습니다.
이 경우에는 싱글톤 서비스 처럼 활성 인스턴스를 제외한 나머지 인스턴스는 비활성 상태가 되며
비활성 인스턴스는 활성 인스턴스가 마무리 될 때까지 락을 기다립니다.
Singleton Service 에서는 한 번에 실행되는 최대 인스턴스 수를 제한하려고 시도합니다.
하지만 경우에 따라서 동시에 중단되는 파드의 수를 제한해야할 수 있습니다.
👍 즉, PodDisruptionBudget 은 일정 수나 비율의 파드가 한 시점에 노드에서 자발적으로 중단되지 않게 보장합니다.
먼저 자발적 중단과 비자발적 중단에 대해서 알아보도록 하겠습니다.
자발적 중단 은 어플리케이션 소유자와 클러스터 관리자가 중단하는 것을 얘기합니다.
먼저 어플리케이션 소유자의 작업은 다음과 같습니다.
다음은 클러스터 관리자의 작업입니다.
비자발적 중단 은 불가피한 상황을 통해서 중단되는 것을 얘기합니다.
다음은 불가피한 상황에서 생기는 중단의 예시입니다.
이와 같이 PodDisruptionBudget 은 자발적 중단이 일어날 때
일시적으로 중단되는 파드의 수를 제한합니다.
이 때 두 가지 옵션을 통해서 제어 할 수 있습니다.
.spec.minAvailable
옵션은 최소한 보장되어야 하는 파드의 개수입니다.
따라서 minAvailable
이 2
라고 한다면 파드가 자발적으로 중단 될 때
남아있는 파드가 2개라면 중단되지 않고 지연됩니다.
다음은 yaml 예시입니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeper
.spec.maxUnavailable
옵션은 동시에 중단 될 최대 파드의 개수입니다.
따라서 maxUnavailable
이 1
라고 한다면 파드가 자발적으로 중단 될 때
중단 대상 파드가 2개라면 하나의 파드만 중단되고 나머지 파드는 중단이 지연됩니다.
다음은 yaml 예시입니다.
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: zk-pdb
spec:
maxUnavailable: 1
selector:
matchLabels:
app: zookeeper
하지만 두 필드를 함께 사용할 수 없으며 PodDisruptionBudget 은 일반적으로
컨트롤러가 관리하는 파드에만 적용되어 Bare Pods 는 다른 제약사항이 고려되야 합니다.
PodDisruptionBudget 은 Quorum 기반과 같이 특정 비율 아래로는
절대 떨어지지 않아야 하는 어플리케이션에 유용합니다.
이번 글을 통해서 쿠버네티스를 통해서 싱글톤 서비스 를 구현하는 법에 대해서 알아보았습니다.
쿠버네티스는 파드의 가용성을 유지하도록 설계가 되었기 때문에 구현이 쉽지 않았지만
어플리케이션 내부와 외부 잠금을 통해서 어느 정도 싱글톤 서비스 의 구현을 시도해보았습니다.
현재 쿠버네티스에서 완벽하게 싱글톤 서비스 를 구현하려면 어플리케이션 내부 잠금 을 통해서 일 것 같은데
클라우드 네이티브에서 원하는 구조는 플랫폼에서 모든 서비스를 관리할 수 있도록 하는 것으로
플랫폼에서 싱글톤 서비스 를 관리 할 수 있도록 하는 것이 필요해보입니다.