ㅁ 스테이트풀(Stateful) Pod 복제
- 레플리카셋은 단일 포드 템플리에서 여러 포드 복제본을 생성
- 복제본은 이름과 IP주소만 다르 뿐 나머지는 동일
- 포트 템플릿에 특정 PersistentVolumClaim을 참조하는 볼륨이 있으면 레플리카셋의 모든 복제본은 동일한 PersistentVolumeClaim사용하므로 Claim에 바인딩된 동일한 PersistentVolume을 사용함
여러 포드의 복제본을 찍어내는 데 사용하는 포드 템플릿에서 이 클레임(Persistent Volume Claim)을 참조하기 때문에 각 복제본이 고유한 PersistentVolumeClaim을 사용하도록 설정 할 수 없다.
ㅁ 스테이트풀셋 이해
레플리카셋 또는 레플리케이션 컨트롤러로 관리하는 포드 복제본은 가축과 같다.
복제본은 대부분 스테이트리스이기 때문에 언제든지 완전히 새로운 포드 복제본으로 교체할 수 있다.
스테이트풀 포드는 실행중인 노드의 장애가 발생하면 다른 노드에서 포드 인스턴스를 부활시켜야 하지만 새 인스턴스는 교체되는 인스턴스와 동일한 이름, 네트워크 ID, 상태를 가져와야 한다.
[안정적인 네트워크 ID 제공]
스테이트풀셋에 의해 생성된 포드는 순성형 색인(제로 기반)이 할당되며, 이 색인을 통해 포드의 이름과 호스트 이름을 부여하고 포드에 안정적인 스토리지를 붙이는데 사용된다.
각 포드의 이름은 스테이트풀셋의 이름과 인스턴스의 서수 인덱스에서 파생되므로 포드의 이름은 예측 가능하다.
ㅇ 스테이트풀 포드 교체
ㅇ 스테이트풀셋 스케일링
스테이트풀셋을 스케일링하면 사용되지 않는 다음 서수 인덱스를 사용해 새 포드 인스턴스가 생성된다.
두 개에서 세 개 인스턴스로 스케일업하면 새 인스턴스에 인덱스 2가 표시된다.
스테이트풀셋 스케일다운의 좋은 점은 어떤 포드를 제거할 것인지 항상 알수 있다.
스테이트풀셋을 스케일다운하면 항상 가장 높은 서수 인덱스가 있는 인스턴스가 제거된다.
따라서 스케일다운 효과를 예측할 수 있다.
[각 스테이트풀 인스턴스에 안정적인 전용 스토리지 제공]
ㅇ 볼륨 클레임 템플릿을 갖춘 포드 템플릿 팀 구성
스테이트풀셋은 포드를 생성하는 것과 동일한 방법으로 PersistentVolumeClaim을 생성해야 함
스테이트풀센은 볼륨 클레임 템플릿을 하나 이상 가질수 있고 이를 통해 포드 인스턴스는 자신만의 PersistentVolumeClaim을 가지면서 생성되는게 가능
ㅇ PersistentVolumeClaim의 생성과 삭제
스테이트풀셋을 하나씩 스케일업하면 두 개 이상의 API 객체(포드와 하위 포드에서 참조하는 하나 이상의 PVC)가 만들어짐
그러나 스케일다운하면 PVC는 남겨 두고 Pod만 삭제한다.
Stateful Pod는 Stateful Application을 실행하기 위한 것이기 때문에 볼륨에 저장하는 데이터가 중요하다는 의미이므로 스테이트풀셋의 스케일 다운에 대한 클레임 삭제는 치명적인 문제가 될 수 있다.
따라서 PersistentVolume을 릴리스 하기 위한 PersistentVolumeClaim은 수동으로 삭제해야 한다.
ㅇ 같은 포드의 새 인스턴스에 영구 볼륨 클레임 다시 붙이기
스케일 다운 이후 PVC가 남아 있다는 것은 이후의 스케일업이 바인딩된 PersistentVolume 및 그 내용을 새 포드 인스턴스에 다시 첨부할 수 있다는 의미이다.
즉 실수로 스테이트풀셋을 스케일다운했다면 다시 스케일업하면 동일한 볼륨을 연결하여 동일한 지속된 상태를 다시 가져올 수 있다.
[스테이트풀셋 사용]
ㅇ 앱 및 컨테이너 이미지 만들기
간단한 스테이트풀 앱: kubia-pet-image/app.js
...
const dataFile ="/var/data/kubia.txt";
...
var handler = function(request, response) {
if(request.method =='POST') {
var file = fs.createWriteStream(datafile);
file.on('open', function (fd) {
request.pipe(file);
console.log("NEw data has been received and stored.");
response.writeHEad(200);
response.end("Data stored on pod " + os.hostname() + "\n");
});
} else {
var data = fileExists(dataFile)
? fs.readFileSync(dataFile, 'utf8')
: "No data poasted yet";
response.write/Head(200)
}
};
var www = http.createServer(handler);
www.listen(8080);
POST 요청을 받을 때마다 요청 본문에 받은 데이터를 /var/data/kubia/txt 파일에 쓴다.
GET 요청을 받으면 호스트 이름과 저장된 데이터(파일의 내용)를 반환 한다.
스테이트풀 애플리케이션용 Dockerfile: kubia-pet-image/Dockerfile
FROM node:7
ADD app.js /app.js
ENTRYPOIONT ["node", "app.js"]
ㅁ 스테이트풀셋을 통한 애플리케이션 배포
애플리케이션을 배포하려면 두가지(또는 세가지) 유형의 객체를 만들어야 함
- 데이터 파일을 저장하는 PersistentVolume
- 스테이트풀셋에 필요한 관리 서비스
- 스테이트풀셋 자체
ㅇ PersistentVolume 생성
GKE를 사용하는 경우 다음과 같이 실제 GCE 영구 디스크를 만들어야 함.
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-a
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-b
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-c
persistent-volumes-gcepd.yaml 파일에서 PersistentVolume 작성
kind: List
apiVersion: v1 # 파일은 세 개의 영구 볼륨 목록을 설명
items:
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-a # 영구 볼륨의 이름은 pv-a, pv-b, pv-c
spec:
capacity:
storage: 1Mi # 각 영구 볼륨의 용량은 1Megabyte
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Reclye # 클레임이 볼륨을 해제하면 다시 사용하기 위해 재사용한다.
gcePersistentDisk: # 볼륨은 GCE 영구 디스크를 기본 스토리지 매커니즘으로 사용한다.
pdName: pv-a
fsType: nfs4
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-b
...
이 매니페스트는 pv-a, pv-b, pv-c 라는 PersistentVolume을 만든다.
GCE 영구 디스크를 기본 스토리지 매커니즘으로 사용하므로 GKE에서만 사용 가능하다.
ㅇ 관리 서비스 생성
스테이트풀셋을 배포하기 전에 스테이트풀 포드에 네트워크 ID를 제공할 때 사용할 헤드리스 서비스를 생성해야 한다.
스테이트풀셋에서 사용할 헤드리스 서비스: kubia-service-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia # 서비스 이름
spec:
clusterIP: None # 스테이트풀셋의 관리 서비스는 헤드리스이어야 한다.
selector: # app=kubia 라벨을 갖는 모든 포드는 이 서비스에 속한다.
app: kubia
ports:
- name: http
port: 80
ClusterIP 필드를 없음으로 설정하면 헤드리스 서비스가 된다.
ㅇ 스테이트풀셋 매니페스트 만들기
스테이트풀셋 매니페스트: kubia-statefulset.yaml
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: kubia
spec:
serviceName: kubia
replicas: 2
template:
metadata:
labels: # 스테이트풀셋에 의해 생성된 포드는 app=kubia 레이블을 가는다.
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia-pet
ports:
- name: http
containerPort: 8080
volumeMounts:
- name: data # 포드 내부의 컨테이너는 이 경로에 pvc 볼륨을 마운트 한다.
mountPath: /var/data
volumeClaimTemplates: # 이 템플릿으로 PersistentVolumeClaim이 작성된다.
- metadata:
name: data
spec:
resources:
request:
storage: 1Mi
accessModes:
- ReadWriteOnce
다른 레플리카셋 또는 디플로이먼트 매니페스트와 큰 차이는 없으나 다른 것은 volumeClaimTempaltes 목록이다.
여기에서 data 라고 하는 볼륨 클레임 템플릿 하나를 정의하고 있으며, 각 템플릿에 대한 Persistent-VolumeClaim을 만드는 데 사용된다.
ㅇ 스테이트풀셋 만들기
$ kubectl create -f kubia-statefulset.yaml
statefulset "kubia" create
이제 포드를 나열해 보면
$ kubectl get po
NAME. READY STATUS RESTARTS AGE
kubia-0 0/1 ContainerCreating 0 1s
두번째 포드는 첫번째 포드가 준비된 후에 만들어진다.
$ kubectl get po
NAME. READY STATUS RESTARTS AGE
kubia-0 0/1 Running 0 1s
kubia-1 1/1 ContainerCreating 0 1s
ㅇ 생성된 스테이트풀 포드 살펴보기
$ kubectl get po kubia-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
...
spec:
containers:
- image: luksa/kubia-pet
...
volumeMounts:
- mountPath: /var/data # 매니페스트에 지정된대로의 볼륨 마운트
name: data
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-2m41
readOnly: true
...
volumes:
- name: data # 스테이트풀셋에 의해 생성된 볼륨
persistentVolumeClaim:
claimName: data-kubia-0 # 클레임이 이 볼륨에 참조됨
- name: default-token-r2m41
secdret:
secretName: default-token-r2m41
ㅇ 생성된 PersistentVolumeClaim 살펴보기
생성된 PersistentVolumeClaims를 나열해 생성된 영구 볼륨 클레임을 확인 한다.
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY. ACCESSMODES. AGE
data-kubia-0 Bound. pv-c 0 37s
data-kubia-1 Bound. pv-a 0 37s
ㅁ 실제로 포드를 동작해 보기
생성한 서비스는 헤드리스이기 때문에 서비스를 통해 포드와 통신할 수 없고 포드에 개별적으로 직접 연결 해야 함.
ㅇ API 서버를 통해 포드와 통신
<apiServerHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>
ㅇ kubectl proxy를 통해 통신
$ kubectl proxy
Strarting to serve on 127.0.0.1:8001
ㅇ kubia-0 포드에 요청 보내기
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
you've hit kubia-0
Data stored on this pod: No data posted yet
애플리케이션이 POST 요청을 받으면 요청의 바디의 내용을 로컬 파일에 저장한다.
ㅇ kubia-0 포드에 POST 요청을 다음과 같이 보낸다.
$ curl -X POST -d "Hey there! This greeting was submitted to kubia-0." localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
Data stored on pod kubia-0
ㅇ GET 요청을 통해 다시 수행할 때 저장된 데이터를 봔환하는지 확인
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
you've hist kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.
ㅇ kubia-1에서의 상태 확인
$ curl localhost:8001/apio/v1/namespaces/default/pods/kubia-1/proxy/
you've hit kubia-1
Data stored on this pod: No data posted yet
ㅁ 재스케줄된 포드가 같은 스토리지에 다시 붙었는지 확인하기 위해 스테이트풀 포드 삭제
kubia-0 포드를 삭제하고 다시 스케줄링 될 때까지 기다린 후 이전과 동일한 데이터를 계속 제공하는지 확인
ㅇ kubia-0 삭제
$ kubectl delete po kubia-0
pod "kubia-0" deleted
ㅇ 포드 목록을 나열하여 종료 여부 확인
$ kubectl get po
NAME. READY. STATUS. RESTARTS. AGE
kubia-0. 1/1 Terminating. 0 3m
kubia-1 1/1 Running. 0 3m
ㅇ 성공적으로 종료되면 바로 스테이트풀셋에서 같은 이름의 새 포드 생성
$ kubectl get po
NAME. READY. STATUS. RESTARTS. AGE
kubia-0. 0/1 ContainerCreating 0 3m
kubia-1 1/1 Running. 0 3m
$ kubectl get po
NAME. READY. STATUS. RESTARTS. AGE
kubia-0 1/1 Running 0 9s
kubia-1 1/1 Running. 0 4m
새 포드는 이전 포드가 스케줄되었던 동일한 노드에 스케줄될 필요가 없으며 클러스터의 어떤 노드라도 스케줄 될 수 있다.
이전 포드의 전체 ID(이름, 호스트 이름 및 스토리지)는 새노드로 이전 된다.
ㅇ 새 포드 실행 후 이름과 영구 데이터 상태 확인
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
you've hist kubia-0
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.
ㅁ 비헤드리스 서비스를 통해 스테이트풀 포드 노출 하기
ㅇ 스테이트풀 포드를 액세스하기 위한 일반 서비스: kubia-service-public.yaml
apiVersion: v1
kind: Service
metadat:
name: kubia-public
spec:
selector:
app: kubia
ports:
- port: 80
targetPort: 8080
이것은 외부에서 노출된 서비스가 이니기 때문에 클러스터 내에서만 액세스 가능하다.
ㅇ API 서버를 통해 클러스터 내부의 서비스 연결
piggyback 포드를 사용해 클러스터 내부에서 서비스에 액세스 하지 않고 API 서버에서 제공하는 것과 동일한 프록시 기능을 사용해 개별 포드에 액세스한 방식으로 서비스에 엑세스할 수 있다.
서비스 프록시 요청 URI 경로는 다음과 같다.
/api/v1/namespaces/<namespace>/services/<service name>/proxy/<path>
$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
You've hit kubia-1
Data stored on. his pod: No data posted yet
[스테이트풀셋에서 피어 발견]
클러스터된 애플리케이션의 중요한 요구 사항은 피어 검색(다른 클러스터 구성원을 찾는 기능)
ㅇ SRV 레코드 소개
SRV 레코드는 특정 서비스를 제공하는 서버의 호스트 이름 및 포트를 가리키는데 사용
쿠버네티스는 헤드리스 서비스를 지원하는 포드의 호스트 이름을 가리키도록 SRV 레코드를 만든다.
$ kubectl run -it srvlookup --image=tutum/dnsutils --rm --rstart=Never -- dig SRV kubia.default.svc.cluster.local
이 명령은 srvlookup이라고 하는 일회용 포드(--restart=Never)를 실행한다. 이 포드는 콘솔(-it)에 연결돼 종료되자 마자 바로 삭제(--rm). 포드는 tutum/dnsutils 이미지에서 단일 컨테이너를 실행하고 다음 명령을 실행한다.
dig SRV kubia.default.svc.cluster.local
명령의 출력은 다음과 같다
...
;; ANSWER SECTION:
k.d.s.c.l. 30 IN SRV. 10 30 0 kubia-0.kubia.default.svc.cluster.local.
k.d.s.c.l. 30 IN SRV. 10 30 0 kubia-1.kubia.default.svc.cluster.local.
;; ADDITIONAL SECTION:
kubia-0.kubia.default.svc.cluster.local. 30 IN A 172.17.0.4
kubia-1.kubia.default.svc.cluster.local. 30 IN A 172.17.0.6
. . .
포드가 스테이트풀셋의 모든 포드 목록을 얻기 위해서 SRV DNS 룩업을 수행하면 된다.
Node.js에서의 룩업은 다음과 같이 수행 한다.
dns.resolveSrv("kubia.default.svc.cluster.local", callBackFunction);
애플리케이션에서 이 명령을 사용해서 각 포드의 피어를 찾을 수 있다.
ㅁ DNS를 통한 피어 검색 구현
샘플 애플리케이션에서 피어 검색하기: kubia-pet-peers-image/app.js
...
const dns = require('dns');
const dataFile = "/var/data/kubia.txt";
const serviceName = "kubia.default.svc.cluster.local";
const port = 8080;
...
var handler = function(request, reponse) {
if (request.method == 'POST'){
..
} else {
response.writeHead(200);
if (request.url == '/data') {
var data = fileExists(dataFile)
? fs.readFilesync(datafile, 'utf8')
: "No data posted yet";
response.end(data);
} else {
response.write("You've hist " + os.hostname() + "\n");
response.write("Data stored in the cluster:\n");
dns.resolveSrv(serviceName, function (err, addresses) { # 앱은 SRV 레코드를 얻기 위해
if (err) { # DNS 룩업을 수행한다.
response.end("Could not look up DNS SRV records: " + err);
return;
}
var numResponses = 0;
if (address.length ==0) {
response.end("No peers discovered.");
} else {
address.forEach(function (item) { # SRV 레코드가 가리키는 각 포드는
var requestOptions = { # 데이터를 획득하기 위해 접촉된다.
host: item.name,
port: port,
path: '/data'
};
httpGet(requestOptions, function (returnedData) {
numResponses++;
response.write("-" + item.name + ": " + returnedData);
response.write("\");
if(numReponses == address.length) {
response.end();
}
});
});
}
});
}
};
...
1. 첫 요청을 받은 서버는 헤드리스 kubia 서비스로 SRV 레코드를 조회
2. 서비스를 지원하는 각 포드에게 GET 요청을 전송
3. 각 노드에 저장된 데이터와 함께 모든 노드의 목록을 반환
ㅁ 스테이트풀셋 업데이트
스테이트풀셋을 업데이트 하려면 kubect edit 명령을 사용한다.
$ kubectl edit statefulset kubia
1. kubectl edit 실행 후 기본 편집기를 통해 스테이트풀셋 정의 파일이 열린다.
2. 정의에서 spec. replicas를 3으로 변경 (기존 2이였으므로 1개를 증가시켜 신규 이미지로 배포)
3. spec.template.spec.containers.image 속성을 새 이미지를 가리키도록 수정
4. 스테이트풀셋을 업데이트하기 위해 파일을 저장하고 편집기를 종료
이전에 두개의 복제본이 실행 중이었으므로 kubia-2라는 추가 복제본이 시작됨
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-0 1/1 Running 0 25m
kubia-1 1/1 Running 0 26m
kubia-2. 0/1 ContainerCreating 0 4s
스테이트풀셋은 레플리카셋과 비슷하고 디플로이먼트와는 비슷하지 않다. 따라서
기존 포드에는 수정이 되지 않는다.
ㅇ 기존 포드를 삭제하여 새 탬플릿으로 포드 인스턴스 생성
$ kubectl delete po kubia-0 kubia-1
pod "kubia-0" deleted
pod "kubia-1" deleted
ㅁ 클러스터된 데이터 스토리지 사용
두 포드가 활성화 되면 기존 post 요청을 통해 정상적으로 작동하는지 확인한다.
$ curl -X POST -d "The sun is shining" localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
Data stored on pod kubia-1
$ curl -X POST -d "The weather is sweet" localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
Data stored on pod kubia-0
아래와 같이 저장된 데이터를 읽어 본다
$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
You've hist kubia-2
Data stored on each cluster node:
- kubia-0.kubia.default.svc.cluster.local: The weather is sweet
- kubia-1.kubia.default.svc.cluster.local: The sun is shining
- kubia-2.kubia.default.svc.cluster.local: No data posted yet
[스테이트풀셋의 노드 장채 처리 방법]
ㅁ 네트워크 노드 연결 해제 시뮬레이션
ㅇ 노드의 eth0 인터페이스를 셧다운하여 노드의 장애를 발생
$ gcloud compute ssh gke-kubia-default-pool-32a2adl3-m0g1
$ sudo ifconfig eth0 down
--> ssh 세션 작동이 멈추기 때문에 계속하려면 다른 터미널을 열어야 한다.
ㅇ 쿠버네티스 마스터에 의해 관찰된 노드의 상태 확인
$ kubectl get node
NAME STATUS AGE VERSION
gek-kubia-default-pool-32a2cac8-506v Ready 16m. v1.6.2
gke-kubia-default-pool-32a2cac8-m0f1 NotReady 16m. v.1.6.2
gke-kubia-default-pool-32a2cac8-sgl7 Ready 1 16m. v.1.6.2
$ kubectl get po
NAME. READY. STATUS. RESTARTS. AGE
kubia-0. 1/1. Unkown 0 15m
kubia-1. 1/1. Running 0 14m
kubia-2. 1/1. Running 0 13m
보는 것처럼 포드는 네트워크 인터페이스를 셧다운한 노드에서 계속 실행 중이기 때문에 kubia-0 포드의 상태를 더 이상 알 수 없다.
ㅇ 상태를 알 수 없는 포드에 발생한 상황
노드가 다시 온라인 상태가 되어 해당 노드와 포드의 상태를 다시 보고하면 해당 노드는 다시 실행됨(Running)으로 표시됨
그러나 포드의 상태를 몇분 이상 알수 없는 상태로 유지하면 포드는 노드에서 자동으로 제거됨.
이것은 마스터에 의해 수행되며 포드 리소스를 삭제해 포드를 제거한다.
Kubelet이 포드가 삭제 표시됨을 확인하면 포드 종료를 시작한다.
ㅁ 수동으로 포드 삭제
ㅇ 일반적인 방법으로 포드 삭제
$ kubectl delete po kubia-0
pod "kubia-0" deleted
ㅇ 노드 Fail로 인한 컨트롤 플레인에 의한 포드 삭제 시
포드의 상태가 Terminating되어 있으며 해당 포드는 이미 삭제 표시가 되어 있으며 해당 노드의 Kubelet이 포드의 컨테이너가 종료됐음을 API 서버에 알리는 즉시 제거가 되나, 노드의 네트워크가 다운됐으므로 이 작업은 발생 할 수 없다.
ㅁ 포드의 강제 삭제
$ kubectl delete po kubia-0 --force --grace-period 0
--force와 --grace-period 0 옵션을 모두 사용해야 한다.
'Kubenetes > Kubernetes Controller' 카테고리의 다른 글
Deployment - 애플리케이션 업데이트 (0) | 2021.02.13 |
---|---|
잡 리소스 및 스케줄링 (0) | 2021.02.11 |
레플리카셋(Replicaset) (0) | 2021.02.11 |
레플리케이션 컨트롤러 (Replication Controller) (0) | 2021.02.11 |