[Kubernetes] StatefulSet (스테이트풀)과 Headless Service (헤드리스 서비스)
🚀 Headless Service
Headless Service란 ?!
DB와 같이 master, slave 구조가 있는 서비스들의 경우 service를 통해 로드밸런싱을 하지 않고 개별 pod의 주소를 알고 접속해야한다.
pod들은 DNS 이름을 가질 수는 있으나 {pod name}.{service name}.{namespace}.svc.cluster.local 형식의 이름을 갖기 때문에 pod를 DNS를 이용해 접근하려면 service name이 있어야 한다.
Statefulset에 의한 서비스들은 service를 이용해서 로드 밸런싱을 하는 것이 아니기 때문에 로드 밸런서의 역할은 필요없고 논리적으로 pod들을 묶어 줄 수 있는 service만 있으면 되기 때문에 헤드리스 서비스를 사용한다.
Headless 서비스를 이용하면 service 가 로드 밸런서의 역할도 하지 않고 단일 IP도 가지지 않지만 nslookup을 이용해서, headless 서비스에 의해서 묶여진 Pod들의 이름도 알 수 있고 {pod name}.{service name}.{namespace}.svc.cluster.local 이름으로, 각 pod 에 대한 접근 주소 역시 얻을 수 있다.
쿠버네티스 생성 시 svc.spec.clusterIP 필드 값을 None으로 설정하면 ClusterIP가 없는 서비스를 만들 수 있다.
헤드리스 서비스의 경우 ClusterIP가 할당되지 않고 kube-proxy가 서비스를 처리하지 않으며 로드 밸런싱 또는 프록시 동작을 수행하지 않는다. DNS가 자동으로 구성되는 방법은 서비스에 셀렉터가 정의되어 있는지 여부에 달려있다.
헤드리스 서비스에 셀렉터 필드를 구성하면 쿠버네티스 API로 확인할 수 있는 엔드포인트(endpoint)가 만들어진다.
서비스와 연결된 파드를 직접 가리키는 DNS A 레코드도 만들어진다.
만약 셀렉터가 없는 경우 엔드포인트가 만들어지지 않는다.
단, 셀렉터가 없더라도 DNS 시스템에는 ExternalName 타입의 서비스에서 사용할 CNAME 레코드가 생성된다.
myweb-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myweb-rs
spec:
replicas: 3
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
myweb-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: myweb-svc
spec:
type: ClusterIP
selector:
app: web
ports:
- port: 80
targetPort: 8080
myweb-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: myweb-svc-headless
spec:
type: ClusterIP
clusterIP: None # <-- Headless Service
selector:
app: web
ports:
- port: 80
targetPort: 8080
일반 service와 Headless service의 차이를 알아보기 위해 리소스를 생성한다.
vagrant@k8s-node1 ~/cont/sts/headless kubectl get rs,po
NAME DESIRED CURRENT READY AGE
replicaset.apps/myweb-rs 3 3 3 23s
replicaset.apps/nfs-client-provisioner-758f8cd4d6 1 1 1 20h
NAME READY STATUS RESTARTS AGE
pod/myweb-rs-cbldm 1/1 Running 0 23s
pod/myweb-rs-djhkw 1/1 Running 0 23s
pod/myweb-rs-svltc 1/1 Running 0 23s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 20h
vagrant@k8s-node1 ~/cont/sts/headless kubectl get svc,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 36h
service/myweb-svc ClusterIP 10.233.3.43 <none> 80/TCP 30s
service/myweb-svc-headless ClusterIP None <none> 80/TCP 13s
NAME ENDPOINTS AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner <none> 20h
endpoints/kubernetes 192.168.100.100:6443 36h
endpoints/myweb-svc 10.233.90.77:8080,10.233.90.79:8080,10.233.92.118:8080 30s
endpoints/myweb-svc-headless 10.233.90.77:8080,10.233.90.79:8080,10.233.92.118:8080 13s
두개의 서비스 myweb-svc
, myweb-svc-headless
가 있다.Headless
SVC는 CLUSTER-IP
값이 None
이다.
두 서비스가 같은 셀렉터를 사용하고 있으므로 같은 ENDPOINTS
구성을 가지고 있다.
vagrant@k8s-node1 ~/cont/sts/headless kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.3.43
myweb-svc
에 DNS 질의를 하면 ClusterIP
주소를 응답한다.
/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.118
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.77
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.79
Headless service
에 DNS를 질의했을 때 응답되는 ip는 누구의 ip일까 ? → 파드의 ip이다.
service의 자체 ip가 없는 것을 Headless servcie
라고 하며 Headless service
를 질의하면 myweb-svc-headless.default.svc.cluster.local
SVC의 ip가 응답하게 된다.
파드가 3개이기 때문에 3개의 endpoint
주소를 응답한다.
vagrant@k8s-node1 ~/cont/sts/headless kubectl scale rs myweb-rs --replicas=5
replicaset.apps/myweb-rs scaled
vagrant@k8s-node1 ~/cont/sts/headless kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.61.67
/ #
스케일링 가능 여부와 상관없이 일반 서비스는 ClusterIP
타입의 service ip를 응답한다.
/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.142
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.115
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.139
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.118
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.92
headless
SVC는 5개의 파드의 ip를 응답하고 있다.
⭐️ 파드가 아닌 StatefulSet
을 연결하면 FQDN
이 모두 달라지고 파드에 이름이 붙는다. ⭐️
MySQL에 Read Replica
를 구현한다고 해보자Master
와 Slave
가 있어야 한다.
클라이언트(일반적으로 Web app)가 데이터를 쓸 때는 마스터에게 요청해야 한다.
읽기를 할 때는 아무곳에서나 읽으면 된다.
StatefulSet
을 통해 마스터와 슬레이브를 구성하게 되면 Headless
SVC를 통해서 두개의 파드를 구별할 수 있게 된다.
🚀 예제 1
vagrant@k8s-node1 ~ kubectl api-resources | grep sts
statefulsets sts apps/v1 true StatefulSet
certificatesigningrequests csr certificates.k8s.io/v1 false CertificateSigningRequest
vagrant@k8s-node1 ~ kubectl explain sts.spec.
serviceName <string> -required-
serviceName
이 필수이다. StatefulSet
은 반드시 Headless service
가 있어야 한다.
myweb-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myweb-sts
spec:
replicas: 3
serviceName: myweb-svc-headless
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
myweb-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: myweb-svc-headless
spec:
type: ClusterIP
clusterIP: None # <-- Headless Service
selector:
app: web
ports:
- port: 80
targetPort: 8080
vagrant@k8s-node1 ~/cont/sts1 kubectl get sts,po
NAME READY AGE
statefulset.apps/myweb-sts 3/3 74s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-0 1/1 Running 0 74s
pod/myweb-sts-1 1/1 Running 0 71s
pod/myweb-sts-2 1/1 Running 0 68s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 34h
기본적으로 StatefulSet
에는 뒤에 서수가 붙는다.
vagrant@k8s-node1 ~/cont/sts1 kubectl delete po myweb-sts-2
pod "myweb-sts-2" deleted
vagrant@k8s-node1 ~/cont/sts1 kubectl get sts,po
NAME READY AGE
statefulset.apps/myweb-sts 2/3 3m1s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-0 1/1 Running 0 3m1s
pod/myweb-sts-1 1/1 Running 0 2m58s
pod/myweb-sts-2 0/1 ContainerCreating 0 2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 34h
myweb-sts-2
를 지우면 종료되며 삭제되고 다시 같은 이름의 파드가 만들어진다.myweb-sts-1
을 지워도 마찬가지로 다시 같은 파드를 만들게된다.
이름이 고정적이다. → 예측 가능하다.
vagrant@k8s-node1 ~/cont/sts1 kubectl scale sts myweb-sts --replicas 4
statefulset.apps/myweb-sts scaled
pod/myweb-sts-3 1/1 Running 0 10s
다음과 같이 스케일링을 하면 예측 가능한 myweb-sts-3
이 만들어진다.
스케일링을 줄여도 마찬가지다 --replicas 2
로 바꾸면 myweb-sts-3
, myweb-sts-2
이 지워지고 지울 때도 절대로 한번에 지우지 않고 순서대로 지운다.
다시 --replicas 4
로 변경하면 myweb-sts-2
가 생성된 후에야 myweb-sts-3
이 생성된다.
--replicas 0
을 설정할 수도 있는데 3 → 2 → 1 → 0 순으로 지워진다.
vagrant@k8s-node1 ~/cont/sts1 kubectl get svc,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 2d2h
service/myweb-svc-headless ClusterIP None <none> 80/TCP 8m18s
NAME ENDPOINTS AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner <none> 34h
endpoints/kubernetes 192.168.100.100:6443 2d2h
endpoints/myweb-svc-headless 10.233.90.96:8080,10.233.92.144:8080,10.233.96.120:8080 8m18s
vagrant@k8s-node1 ~/cont/sts1 kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
myweb-sts-0 1/1 Running 0 9m51s 10.233.90.96 node1 <none> <none>
myweb-sts-1 1/1 Running 0 9m48s 10.233.96.120 node2 <none> <none>
myweb-sts-2 1/1 Running 0 6m52s 10.233.92.144 node3 <none> <none>
nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 34h 10.233.92.110 node3 <none> <none>
Headless Service
의 ENDPOINTS
와 파드의 ip가 매칭되는 것을 확인할 수 있다.
vagrant@k8s-node1 ~/cont/sts1 kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.144
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.12
/ # host myweb-sts-0.myweb-svc-headless
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96
/ # host myweb-sts-0.myweb-svc-headless.default.svc.cluster.local
myweb-sts-0.myweb-svc-headless.default.svc.cluster.local has address 10.233.90.96
/ # host myweb-sts-1.myweb-svc-headless.default.svc.cluster.local
myweb-sts-1.myweb-svc-headless.default.svc.cluster.local has address 10.233.96.120
/ # host myweb-sts-2.myweb-svc-headless.default.svc.cluster.local
myweb-sts-2.myweb-svc-headless.default.svc.cluster.local has address 10.233.92.144
파드의 이름으로 쿼리를 보낼 수 있다.FQDN
은 myweb-sts-0.myweb-svc-headless.default.svc.cluster.local
이다. 즉, 파드를 특정할 수 있다.
파드 집합의 배포와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다.
이름이 고유하므로 특정 파드를 제거하면 그 이름과 똑같은 파드가 만들어진다.
리소스마다 붙는 id도 똑같은 id로 만들어진다. id가 같다는 것은 배치되는 노드도 같다는 것이다.
어떤 하나의 리소스를 만들 때 id가 부여되면 그 id는 특정 노드에서만 실행할 수 있다.
그것이 스케줄러에 의해 고정이 되어있기 때문이다.
🚀 StatefulSet
스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용하다.
- 안정된, 고유한 네트워크 식별자.
- 안정된, 지속성을 갖는 스토리지.
- 순차적인, 정상 배포(graceful deployment)와 스케일링.
- 순차적인, 자동 롤링 업데이트.
제한사항StatefulSet
에 붙이는 스토리지는 반드시 pvc
여야 한다. emptyDir
, hostPath
는 붙일 수 없다.
스토리지 클래스가 존재해야 하며 동적 프로비저닝이 구성되어 있어야 한다.
파드를 삭제해도 볼륨이 삭제되지 않는다. 파드가 삭제돼도 고유성이 지켜져야하기 때문에 데이터를 유지하기 위해서다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myweb-rs
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: myweb
image: httpd
volumeMounts:
- name: myvol
mountPath: /usr/local/apache2/htdocs
volumes:
- name: myvol
persistentVolumeClaim:
claimName: mypvc-dynamic
다음 파일을 통해 ReplicaSet
으로 3개의 파드가 같은 이미지로부터 만들어질 것이다.
즉, myvol
이라는 볼륨이 있고 이 볼륨을 다 똑같이 마운팅시킨다.
이때의 각 파드는 고유성이 없다. 파드의 이름은 다르지만 각 파드가 가지고 있는 데이터가 전부 같은 데이터이다.
데이터의 입장에서는 각 파드가 전혀 다르지않다.
임의의 파드가 a라는 데이터를 쓰기 했다면 다른 파드들도 a 데이터를 갖게된다.
실제로 고유성이 있으려면 파드마다 별도의 볼륨을 가지고 있어야 한다. 그래야만 모든 파드가 고유해진다.
만약 파드가 DB App이라고 해보자
DB App은 누구에게 질의해도 같은 값을 응답해야 한다.
이런 관점에서 스토리지의 내용, 데이터를 동기화 할 것인가는 선택사항이다.
중요한 것은 StatefulSet
은 각자가 고유해야 한다. 각자가 고유하기 위해서는 별도의 스토리지가 있어야 한다.
vagrant@k8s-node1 ~/cont/sts1 kubectl explain sts.spec.
volumeClaimTemplates <[]Object>
volumeClaimTemplates
: pvc
를 만들 때 사용할 템플릿
pvc
가 만들어지면 동적 프로비저닝을 구성했을 때 pv
가 따로 만들어진다.
고유한 데이터를 별도로 가질 수 있게 된다.
vagrant@k8s-node1 ~/cont/sts1 kubectl explain sts.spec.volumeClaimTemplates
pvc
를 만들기위한 FIELDS
가 있다.
vagrant@k8s-node1 ~/cont/sts1 kubectl explain sts.spec.volumeClaimTemplates.spec
pvc
의 spec
이다.
파드가 삭제되더라도 볼륨(pv
, pvc
)을 삭제하지 않는 이유는 다시 스케일링을 하거나 다시 파드가 만들어지는 경우 해당되는 고유한 데이터를 써야하기 때문이다.
STS에 의해 파드를 만들고 반드시 Headless service
를 만들어서 연결시켜야 한다.
그 외의 일반 SVC를 만들어서 붙이는 것은 선택 사항이다.
🚀 예제2 : PVC 템플릿
myweb-sts-vol.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myweb-sts-vol
spec:
replicas: 3
serviceName: myweb-svc-headless
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080
protocol: TCP
volumeMounts:
- name: myweb-pvc
mountPath: /data
volumeClaimTemplates:
- metadata:
name: myweb-pvc
spec:
accessMode:
- ReadWriteOnce
resources:
requests:
storage: 1G
storageClassName: nfs-client
ReplicaSet
이나 Deployment
의 경우 여러개의 파드가 동시에 쓰기를 해야하는 경우가 생길 수 있어서 RWX라는 것이 필요했지만, 지금 상황은 파드가 자신만의 공간을 사용하는 것이기 때문에 굳이 Many를 세팅하지 않아도 상관없다.
ReadWriteOnce
면 충분하다.
vagrant@k8s-node1 ~/cont/sts2 kubectl get sts,po,pv,pvc
NAME READY AGE
statefulset.apps/myweb-sts-vol 3/3 15s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-vol-0 1/1 Running 0 15s
pod/myweb-sts-vol-1 1/1 Running 0 11s
pod/myweb-sts-vol-2 1/1 Running 0 9s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 21h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-2 nfs-client 9s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-1 nfs-client 11s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-0 nfs-client 14s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0 Bound pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO nfs-client 15s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1 Bound pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO nfs-client 11s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2 Bound pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO nfs-client 9s
vagrant@k8s-node1 ~/cont/sts2 kubectl scale sts myweb-sts-vol --replicas=2
statefulset.apps/myweb-sts-vol scaled
vagrant@k8s-node1 ~/cont/sts2 kubectl get sts,po,pv,pvc
NAME READY AGE
statefulset.apps/myweb-sts-vol 2/2 85s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-vol-0 1/1 Running 0 85s
pod/myweb-sts-vol-1 1/1 Running 0 81s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 21h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-2 nfs-client 79s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-1 nfs-client 81s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-0 nfs-client 84s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0 Bound pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO nfs-client 85s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1 Bound pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO nfs-client 81s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2 Bound pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO nfs-client 79s
파드는3 → 2개로 줄었으나 pv
와 pvc
는 여전히 3개이다. 데이터를 보존하기위해 볼륨을 자동으로 지우지 않는다.
vagrant@k8s-node1 ~/cont/sts2 kubectl scale sts myweb-sts-vol --replicas=3
statefulset.apps/myweb-sts-vol scaled
vagrant@k8s-node1 ~/cont/sts2 kubectl get sts,po,pv,pvc
NAME READY AGE
statefulset.apps/myweb-sts-vol 3/3 2m2s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-vol-0 1/1 Running 0 2m2s
pod/myweb-sts-vol-1 1/1 Running 0 118s
pod/myweb-sts-vol-2 1/1 Running 0 2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 21h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-2 nfs-client 116s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-1 nfs-client 118s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-0 nfs-client 2m1s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0 Bound pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO nfs-client 2m2s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1 Bound pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO nfs-client 118s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2 Bound pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO nfs-client 116s
다시 --replicas=3
으로 스케일링을 하면 기존의 pvc
를 사용하게 된다.
myweb-sts-vol-2
이 생성됐지만 기존의 pvc
를 사용하므로 여전히 pvc
, pv
는 3개인 것을 확인할 수 있다.
vagrant@k8s-node1 ~/cont/sts2 kubectl scale sts myweb-sts-vol --replicas=4
statefulset.apps/myweb-sts-vol scaled
vagrant@k8s-node1 ~/cont/sts2 kubectl get sts,po,pv,pvc
NAME READY AGE
statefulset.apps/myweb-sts-vol 3/4 3m
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-vol-0 1/1 Running 0 3m
pod/myweb-sts-vol-1 1/1 Running 0 2m56s
pod/myweb-sts-vol-2 1/1 Running 0 60s
pod/myweb-sts-vol-3 0/1 ContainerCreating 0 2s
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 21h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-2 nfs-client 2m54s
persistentvolume/pvc-270b33ca-6116-4961-8bcb-80e5b44e7517 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-3 nfs-client 2s
persistentvolume/pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-1 nfs-client 2m56s
persistentvolume/pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-0 nfs-client 2m59s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0 Bound pvc-eb4d6346-a412-4697-b2ed-371e726c4826 1G RWO nfs-client 3m
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1 Bound pvc-bd135851-a4b5-4259-be3e-c8dd9d50eb49 1G RWO nfs-client 2m56s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-2 Bound pvc-0e81dc88-94ee-4b7b-af51-3f73d7f8f5c4 1G RWO nfs-client 2m54s
persistentvolumeclaim/myweb-pvc-myweb-sts-vol-3 Bound pvc-270b33ca-6116-4961-8bcb-80e5b44e7517 1G RWO nfs-client 2s
--replicas=4
로 조정하면 pv
, pvc
가 4개가 된다.
vagrant@k8s-node1 ~/cont/sts2 kubectl exec myweb-sts-vol-0 -it -- sh
/ # cd /data/
/data # ls
/data # touch a b c
/data # ls
a b c
/data # exit
vagrant@k8s-node1 ~/cont/sts2 kubectl exec myweb-sts-vol-1 -it -- sh
/ # cd /data/
/data # ls
/data # touch x y z
/data # ls
x y z
/data # exit
myweb-sts-vol-1
에서는 myweb-sts-vol-0
에서 사용하던 데이터를 볼 수 없다.
vagrant@k8s-node1 ~/cont/sts2 kubectl exec myweb-sts-vol-2 -it -- sh
/ # cd /data/
/data # ls
/data # exit
마찬가지로 myweb-sts-vol-0
, myweb-sts-vol-1
에서 사용하던 데이터를 볼 수 없다.
vagrant@k8s-node1 ~/cont/sts2 kubectl delete po myweb-sts-vol-1
pod "myweb-sts-vol-1" deleted
vagrant@k8s-node1 ~/cont/sts2 kubectl get po
NAME READY STATUS RESTARTS AGE
myweb-sts-vol-0 1/1 Running 0 7m10s
myweb-sts-vol-1 1/1 Running 0 4s
myweb-sts-vol-2 1/1 Running 0 5m10s
myweb-sts-vol-3 1/1 Running 0 4m12s
nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 21h
vagrant@k8s-node1 ~/cont/sts2 kubectl exec myweb-sts-vol-1 -it -- sh
/ # cd /data/
/data # ls
x y z
myweb-sts-vol-1
파드를 지우고 새롭게 만들어진 같은 이름의 파드는 앞서 만들어둔 데이터를 볼 수 있다.
즉, 고유성을 보장할 수 있다.
🚀 예제3 : One Master Multi Slave
STS를 통해 MySQL 노드를 생성한다. Master는 R/W가 가능하고 나머지는 다 Slaver로 Read Only이다.
이러한 구성을 One Master Multi Slave라고 한다.
https://kubernetes.io/docs/tasks/run-application/run-replicated-stateful-application/
다음 문서를 참고하여 구성한다.
vagrant@k8s-node1 ~/cont/sts3 kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
configmap/mysql created
vagrant@k8s-node1 ~/cont/sts3 kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 8d
mysql 2 4s
configMap
을 생성한다. mysql
이라는 이름으로 생성됐다.
vagrant@k8s-node1 ~/cont/sts3 kubectl describe cm mysql
Name: mysql
Namespace: default
Labels: app=mysql
Annotations: <none>
Data
====
primary.cnf:
----
# Apply this config only on the primary.
[mysqld]
log-bin
replica.cnf:
----
# Apply this config only on replicas.
[mysqld]
super-read-only
BinaryData
====
Events: <none>
다음과 같이 두개의 데이터가 들어있는 것을 확인할 수 있다.
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
mysql
, mysql-read
라는 SVC를 생성한다. ClusterIP
값이 None
이므로 Headless service
를 의미한다.
mysql-read
는 ClusterIP
를 설정하지 않았으므로 일반 SVC이다.
타입을 지정하지 않았기 때문에 두 SVC모두 ClusterIP
타입이며 두개의 SVC를 만들고 같은 셀렉터를 사용하는 방식이다. 즉, 같은 파드를 가리킨다.
Headless service
를 만드는 이유는 StatefulSet
을 만들기 때문이며 StatefulSet
의 Master
와 Slave
를 구분하기 위해 Headless service
를 사용한다.
읽기 작업은 Master
와 Slave
모두에서 가능하므로 mysql-read
는 별도의 Headless service
를 사용하지 않는다.
vagrant@k8s-node1 ~/cont/sts3 kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 2d19h
mysql ClusterIP None <none> 3306/TCP 29h
mysql-read ClusterIP 10.233.11.154 <none> 3306/TCP 29h
mysql
은 CLUSTER-IP
가 None
인 것을 확인할 수 있다.
vagrant@k8s-node1 ~/cont/sts3 kubectl get svc,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 2d19h
service/mysql ClusterIP None <none> 3306/TCP 29h
service/mysql-read ClusterIP 10.233.11.154 <none> 3306/TCP 29h
NAME ENDPOINTS AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner <none> 2d3h
endpoints/kubernetes 192.168.100.100:6443 2d19h
endpoints/mysql 10.233.92.127:3306,10.233.96.100:3306 29h
endpoints/mysql-read 10.233.92.127:3306,10.233.96.100:3306 29h
아직은 STS을 만들지 않아서 없다.
vagrant@k8s-node1 ~/cont/sts3 kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
statefulset.apps/mysql created
이제 STS을 만든다. 이제 구성은 끝났다.
⭐️ 공식 문서와 다름 주의 !!
vagrant@k8s-node1 ~/cont/sts3 wget https://k8s.io/examples/application/mysql/mysql-configmap.yaml
공식문서의 명령을 실행하는 대신 wget
명령으로 mysql-configmap.yaml
파일을 받는다.
mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
primary.cnf: |
# Apply this config only on the primary.
[mysqld]
log-bin
replica.cnf: |
# Apply this config only on replicas.
[mysqld]
super-read-only
기존 YAML 파일에서 디렉토리 경로가 잘못되었으므로 datadir=/var/lib/mysql/mysql
전체 라인을 삭제하거나datadir=/var/lib/mysql
로 수정한다.
vagrant@k8s-node1 ~/cont/sts3 kubectl get sts,po,pv,pvc,svc,ep
NAME READY AGE
statefulset.apps/mysql 2/2 90s
statefulset.apps/myweb-sts 3/3 17h
NAME READY STATUS RESTARTS AGE
pod/mysql-0 2/2 Running 0 90s
pod/mysql-1 2/2 Running 1 (34s ago) 49s
pod/myweb-sts-0 1/1 Running 0 17h
pod/myweb-sts-1 1/1 Running 0 17h
pod/myweb-sts-2 1/1 Running 0 17h
pod/nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 2d3h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-4a3ed202-ec2c-43bc-b3d0-c8cf56d3ee2c 10Gi RWO Delete Bound default/data-mysql-1 nfs-client 49s
persistentvolume/pvc-bf8333bc-491d-4ef3-8f96-36ef648cfbf7 10Gi RWO Delete Bound default/data-mysql-0 nfs-client 90s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mysql-0 Bound pvc-bf8333bc-491d-4ef3-8f96-36ef648cfbf7 10Gi RWO nfs-client 90s
persistentvolumeclaim/data-mysql-1 Bound pvc-4a3ed202-ec2c-43bc-b3d0-c8cf56d3ee2c 10Gi RWO nfs-client 49s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 2d19h
service/mysql ClusterIP None <none> 3306/TCP 29h
service/mysql-read ClusterIP 10.233.11.154 <none> 3306/TCP 29h
NAME ENDPOINTS AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner <none> 2d3h
endpoints/kubernetes 192.168.100.100:6443 2d19h
endpoints/mysql 10.233.92.146:3306,10.233.96.125:3306 29h
endpoints/mysql-read 10.233.92.146:3306,10.233.96.125:3306 29h
다음과 같이 생성된 리소스들을 확인한다.
ClusterIP
타입이므로 접속을 위해서는 컨테이너를 띄워야 한다.
vagrant@k8s-node1 ~/cont/sts3 kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # host mysql
mysql.default.svc.cluster.local has address 10.233.92.127
mysql.default.svc.cluster.local has address 10.233.96.100
/ # host mysql-read
mysql-read.default.svc.cluster.local has address 10.233.11.154
/ #
/ # mysql -h mysql-0.mysql -u root
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 51
Server version: 5.7.38-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.010 sec)
MySQL [(none)]> create database sohui;
Query OK, 1 row affected (0.005 sec)
MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sohui |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.002 sec)
MySQL [(none)]> exit
Bye
/ # mysql -h mysql-1.mysql -u root
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 88
Server version: 5.7.38 MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sohui |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.007 sec)
MySQL [(none)]> drop database sohui;
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement
MySQL [(none)]> exit
Bye
두번째 파드로 진입해서 데이터베이스를 확인하면 첫번째 파드에서 만든 sohui
데이터베이스를 확인할 수 있다.
두번째 파드에서는 drop database
를 할 수 없다. 해당 파드는 쓰기 작업을 할 수 없기 때문이다.
/ # mysql -h mysql-0.mysql -u root
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 125
Server version: 5.7.38-log MySQL Community Server (GPL)
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sohui |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.006 sec)
MySQL [(none)]> drop database sohui;
Query OK, 0 rows affected (0.009 sec)
MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.002 sec)
MySQL [(none)]> exit
Bye
다시 첫번째 파드에서 sohui
데이터베이스를 drop
하면 실행되며 두번째 파드에서 데이터베이스를 봐도 해당 데이터베이스가 삭제된 것을 확인할 수 있다.
확장
vagrant@k8s-node1 ~/cont/sts3 kubectl scale sts mysql --replicas 3
statefulset.apps/mysql scaled
vagrant@k8s-node1 ~/cont/sts3 kubectl get po
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 17m
mysql-1 2/2 Running 1 (16m ago) 16m
mysql-2 0/2 Init:1/2 0 4s
nfs-client-provisioner-758f8cd4d6-wpjbt 1/1 Running 0 2d3h
복제본 개수를 3개로 확장시켜본다.
vagrant@k8s-node1 ~/cont/sts3 kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
If you don't see a command prompt, try pressing enter.
/ # mysql -h mysql-2.mysql -u root -e 'select * from sohui.message;'
+-------------+
| message |
+-------------+
| hello mysql |
+-------------+
/ #
컨테이너를 띄우고 방금 생성한 mysql-2
에 접속해본다. mysql-2
에도 동기화가 완료되어 테이블과 데이터가 만들어진 것을 확인할 수 있다.
아무런 작업도 하지 않은 mysql-2
에서도 mysql-0
, mysql-1
과 동일한 데이터를 사용할 수 있다.
즉, 스케일링을 하게되면 만들어지자마자 바로 동기화가 된다.
🚀 리소스 정리
vagrant@k8s-node1 ~/cont/sts2 kubectl delete -f .
statefulset.apps "myweb-sts-vol" deleted
service "myweb-svc-headless" deleted
vagrant@k8s-node1 ~/cont/sts2 kubectl delete pvc --all
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-0" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-1" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-2" deleted
persistentvolumeclaim "myweb-pvc-myweb-sts-vol-3" deleted
vagrant@k8s-node1 ~/cont/sts2 kubectl get pv,pvc
No resources found