Singleton Service Pattern이란?
SW 개발에 사용되는 생성 패턴 중 하나로, 한 번에 하나의 애플리케이션 인스턴스만 실행한다.
예시) 메세지 대기열의 메세지를 순차적으로 소비해야하는 웹 애플리케이션은 한 번에 둘 이상의 인스턴스를 실행하지 않아야한다. 쿠버네티스에서는 외부 잠금과 내부 잠금을 통해 2가지 레벨로 구현할 수 있다.
문제점
- 쿠버네티스의 주요 기능은 다중 인스턴스를 실행해서 시스템의 처리량과 가용성을 높이는 것
- Deployment, ReplicaSet과 같은 리소스로는 싱글톤 서비스를 사용하는 데 어려움이 있음
- 동시에 하나의 서비스 인스턴스만 실행되어야하는 경우가 있음
애플리케이션 외부 잠금(비 인식)
애플리케이션 외부의 관리 프로세스를 사용하여, 오직 애플리케이션 하나의 인스턴스만 실행하게 하는 방법이다. Pod 내부에서 실행 중인 컨테이너는 자신이 실행 중인 유일한 파드임을 알지 못하고, 다른 인스턴스가 실행되지 않도록 하는 메커니즘을 인식하지 못하기 때문에 비인식(non applicaion-aware method)라고도 한다.
쿠버네티스에서 ReplicaSet
과 StatefulSet
을 통해서 구현이 가능하다.
ReplicaSet
ReplicaSet을 사용하는 것이 Singleton Service를 사용하는 가장 간단한 방법으로, .spec.replicas
를 1로 설정하면 된다. 단 ReplicaSet은 고가용성과 무중단을 목적으로 하기 때문에, 최소 1개를 보장할 뿐, 1개 이상의 파드가 생성될 수 있다.
- 클러스터와 노드의 연결이 끊어진 경우
- 파드의 spec이 변경되어 RollingUpdate 하는 경우
다음과 같이 replicas: 1
로 정의된 ReplicaSet을 생성함으로써 싱글톤 패턴을 사용할 수 있다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
spec:
replicas: 1 ##replicas를 1로 설정
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: gcr.io/google_samples/gb-frontend:v3
그러나 앞서 언급했듯이, 경우에 따라 파드가 1개 이상 생성될 수 있기 때문에, ReplicaSet은 Singleton Pattern을 구현하기 위한 최상의 옵션이 아닐 수 있다.
StatefulSet
ReplicaSet은 일관된 상태보다는 애플리케이션의 고가용성을 유지하는 경향이 있기 때문에, Singleton Pattern을 따르는 애플리케이션의 요구사항이 매우 엄격한 경우에는 StatefulSet을 사용해야한다.
StatefulSet에 대한 자세한 설명은 여기에서 살펴볼 수 있고, 간단하게만 설명하자면 다음과 같다.
- 애플리케이션의 statefule을 관리하는 워크로드 API 오브젝트
- 파드의 순서 및 고유성을 보장(동일한 이름과 동일한 ID로 동일한 노드에 생성)
- Headless 서비스(Cluster IP가 없는 서비스)가 필요
Singleton Service에서의 StatefulSet
(1) 일관된 볼륨 : 파드를 삭제할 때, 파드와 연결된 볼륨이 자동으로 삭제되지 않아 수동으로 제거. 관리 부담이 추가되나, 파드의 일관성 유지 가능
(2) 안정적인 네트워크 : 특성상 Headless 서비스를 이용해야하고, 이로 인해 파드의 네트워크 ID(DNS 주소)를 유지할 수 있다.
- 예시 :
my-singleton.default.svc.cluster.local
- 일반적으로 서비스를 질의(host)하면 서비스의 IP가 출력되지만, 헤드리스 서비스는 서비스의 IP가 아닌 파드의 주소가 출력된다. 즉 파드들을 구분할 수 있도, DNS 레코드를 통해 싱글톤 파드에 직접 액세스할 수 있다.
StatefuleSet은 Pod의 시작 및 종료 순서에 대해 더 엄격하여 ReplicaSet 보다 Singleton 패턴을 준수하도록 보장한다. 마찬가지로 .spec.replicas
를 설정하고, headless 서비스를 함께 생성한다.
cloudclub-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cloudclub-sts
spec:
replicas: 1
serviceName: cloudclub-svc-headless
selector:
matchLabels:
app: cloudclub
template:
metadata:
labels:
app: cloudclub
spec:
containers:
- name: cloudclub
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
cloudclub-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: cloudclub-svc-headless
spec:
type: ClusterIP
clusterIP: None # <-- Headless Service
selector:
app: cloudclub
ports:
- port: 80
targetPort: 8080
생성하면 다음과 같이 파드가 1개 생성된 것을 확인할 수 있고,
참고로 StatefulSet으로 생성된 파드는 cloudclub-sts-0
처럼 이름 뒤에 서수가 붙게된다.
$ kubectl get svc,po
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/cloudclub-svc-headless ClusterIP None <none> 80/TCP 11s
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 27h
NAME READY STATUS RESTARTS AGE
pod/cloudclub-sts-0 1/1 Running 0 6s
애플리케이션 내부 잠금
주어진 시간에 하나의 인스턴스(일반적으로 리더라고 함)만 실행하도록 응용 프로그램 자체를 설계하는 방법이다. 외부 잠금에 비해, 내부 잠금 방식은 애플리케이션이 인식하고 있기 때문에 인식(application-aware method)라고도 한다.
분산 잠금(Distributed Lock)
애플리케이션 하나의 인스턴스만 활성 상태이고, 나머지는 비활성 상태로 대기하는 방법이다. 활성 인스턴스가 다운되면, 나머지 인스턴스는 인계할 새 인스턴스를 선택하게 된다. 이러한 분산 잠금 방식을 사용하기 위해서는 zookeeper, redis, etcd 등의 distributed lock 내용이 구현되어있는 프로그램 또는 프레임 워크가 필요하다.
Etcd를 사용하는 방법을 간단하게나마 살펴보자.
- Etcd란, 쿠버네티스가 상태를 유지하기 위해 사용하는 key-value 저장소
- Etcd의 Distributed Lock 메커니즘을 사용할 수 있도록 Apache Camel이 Connector를 제공
- Connector는 Optimistic locking guarantee를 통해 하나의 인스턴스만 활성화 할 수 있도록 함
PodDistributionBudget(PDB)
PDB는 싱글톤 패턴과 관련이 크게 없지만, 애플리케이션 내부적으로 제어할 수 있는 수단 중 하나여서 본 챕터에 포함된 것 같다. 싱글톤 패턴과 별개로 내부적으로 중단을 제한할 때 사용하는 방식 중 한가지라고 생각하면 될 것 같다.
경우에 따라 동시에 중단되는 파드의 수를 제한해야할 수 있다.
(예 : 파드 중단으로 최소 개수가 유지되지 않아 애플리케이션 중단)
PDB는 일정 수 또는 일정 비율의 파드가 한 시점에 노드에서 자발적으로 중단되지 않도록 보장한다. 따라서 특정 비율 아래로는 절대 떨어지지 않아야 하는 애플리케이션에 유용하게 사용할 수 있다.
대표적으로 다음의 2가지 필드를 살펴보자.
minAvailable
.spec.minAvailable
- 최소한 보장되어야하는 파드의 개수
- 예시 : 파드가 자발적으로 중단될 때 남아있는 파드가 4개라면, 중단되지 않고 지연된다.
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: mycluster-pdb
spec:
minAvailable: 4
selector:
matchLabels:
app: mycluster
maxUnavailable
.spec.maxUnavailable
- 누락될 수 있는 최대 파드의 개수
- 예시 : 파드가 자발적으로 중단될 때 2개의 파드만 중단되고 나머지 파드는 중단되지 않고 지연된다.
apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
name: mycluster-pdb
spec:
minUnavailable: 2
selector:
matchLabels:
app: mycluster
Reference