
수동으로 POD 생성
: pod를 관리하는 replicaSet이 없어서 노드 장애가 발생하면 수동으로 관리해줘야한다.
replicaSet당 POD 한 개
: 각각의 pod가 replicaSet과 PVC를 가질 수 있지만, 여러 개의 replicaSet을 생성하는 것은 번거롭다.
동일한 볼륨에서 여러 개의 디렉토리 사용
: pod 간의 디렉토리 조정이 필요, 공유되는 볼륨에 병목현상
각 POD에 대한 전용 service
: 여러 개의 service를 생성하는 것 또한 번거롭다.
애완동물 vs 가축
stateless pod = 가축
인스턴스가 죽어도 새로운 pod로 대체되면 차이를 느끼지 못한다.
ReplicaSet / ReplicationController는 가축처럼 pod를 관리
stateful pod = 애완동물
동일한 상태의 인스턴스로 대체하지 않으면 안된다.
StatefulSet은 안정적인 정체성을 갖는 애완동물처럼 pod를 관리

GOVERNING HEADLESS SERVICE
: 클러스터 IP가 할당되지 않고, StatefulSet에 있는 모든 pod에게 실제 네트워크 identity 제공. 로드밸런싱/프록시를 하지 않는다. pod에 자체 dns를 제공하여 각 파드에 접근할 수 있게 한다.
동일한 호스트네임을 가진 pod로 교체



PERSISTENTVOLUMECLAIMS의 생성 및 삭제
동일한 새 인스턴스에 PERSISTENTVOLUMECLAIM을 다시 부착

...
const dataFile = "/var/data/kubia.txt";
...
var handler = function(request, response) {
if (request.method == 'POST') { // 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 { // GET 요청과 기타 요청들
var data = fileExists(dataFile)
? fs.readFileSync(dataFile, 'utf8')
: "No data posted yet";
response.writeHead(200);
response.write("You've hit " + os.hostname() + "\n");
response.end("Data stored on this pod: " + data + "\n");
}
};
var www = http.createServer(handler);
www.listen(8080);
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b pv-a
pv-b, pv-c도 생성
kind: List # 볼륨 3개를 작성
apiVersion: v1
items:
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-a # 볼륨이름
spec:
capacity: # 용량
storage: 1Mi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # 반환 정책
gcePersistentDisk: # gce 디스크 사용
pdName: pv-a
fsType: nfs4
- apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-b
...
apiVersion: v1
kind: Service
metadata:
name: kubia
spec:
clusterIP: None # headless service
selector:
app: kubia
ports:
- name: http
port: 80
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: kubia
spec:
serviceName: kubia
replicas: 2
template:
metadata:
labels:
app: kubia
spec:
containers:
- name: kubia
image: luksa/kubia-pet
ports:
- name: http
containerPort: 8080
volumeMounts:
- name: data
mountPath: /var/data # 해당 경로에 pvc 볼륨 마운트
volumeClaimTemplates: # PVC 정의
- metadata:
name: data
spec:
resources:
requests:
storage: 1Mi
accessModes:
- ReadWriteOnce
$ kubectl get po kubia-0 -o yaml
apiVersion: v1
kind: Pod
metadata:
...
spec:
containers:
- image: luksa/kubia-pet
...
volumeMounts:
- mountPath: /var/data # pvc 볼륨 마운트 해준 경로
name: data
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: default-token-r2m41
readOnly: true
...
volumes:
- name: data # StatefulSet에서 생성한 볼륨
persistentVolumeClaim:
claimName: data-kubia-0
- name: default-token-r2m41
secret:
secretName: default-token-r2m41
<apiServerHost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
# GET
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
# POST
$ curl -X POST -d "Hey there! This greeting was submitted to kubia-0."\
➥ localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/

$ kubectl delete po kubia-0
$ kubectl proxy
Starting to serve on 127.0.0.1:8001
$ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/
Data stored on this pod: Hey there! This greeting was submitted to kubia-0.
apiVersion: v1
kind: Service
metadata:
name: kubia-public
spec:
selector:
app: kubia
ports:
- port: 80
targetPort: 8080
/api/v1/namespaces/<namespace>/services/<service name>/proxy/<path>
❗ 서비스를 통한 요청은 매번 랜덤한 클러스터 노드로 도달한다.
: 특정 서비스를 제공하는 서버를 식별. 서버의 hostname과 port를 가리키는 데 사용
# SRV DNS lookup
$ kubectl run -it srvlookup --image=tutum/dnsutils --rm
➥ --restart=Never -- dig SRV kubia.default.svc.cluster.local
# 명령어 출력 결과
...
;; ANSWER SECTION:
kubia.default.svc.cluster.local. 30 IN SRV 10 33 0 kubia-0.kubia.default.svc.cluster.local.
kubia.default.svc.cluster.local. 30 IN SRV 10 33 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
...
일반 서비스를 통해 게시한 데이터는 랜덤한 클러스터 노드에 저장된다. 모든 클러스터 노드를 확인하려면 모든 peer를 찾아야 하는데, 이 때 SRV 레코드 사용
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, response) {
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 hit " + os.hostname() + "\n");
response.write("Data stored in the cluster:\n");
dns.resolveSrv(serviceName, function (err, addresses) { # DNS lookup
if (err) {
response.end("Could not look up DNS SRV records: " + err);
return;
}
var numResponses = 0;
if (addresses.length == 0) {
response.end("No peers discovered.");
} else { # SRV 레코드를 얻었다면 사용해서 pod 지정
addresses.forEach(function (item) {
var requestOptions = {
host: item.name,
port: port,
path: '/data'
};
httpGet(requestOptions, function (returnedData) {
numResponses++;
response.write("- " + item.name + ": " + returnedData);
response.write("\n");
if (numResponses == addresses.length) {
response.end();
}
...
$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/
You've hit 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
이제 클라이언트 요청이 클러스터 노드 중 하나에 도달하면 모든 peer에서 데이터를 수집해서 클라이언트로 응답한다.
# StatefulSet 편집기 실행
$ kubectl edit statefulset kubia
spec.replicas와 spec.template.spec.containers.image 수정
# pod 복제본 확인 - 기존 pod는 업데이트x
$ 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
# pod 수동 삭제 후 재생성
$ kubectl delete po kubia-0 kubia-1
➕ 쿠버네티스 1.7부터는 StatefulSet도 롤링 업데이트 지원
쿠버네티스는 statefule pod를 대체하기 전에 정말 pod가 실행되지 않고 있는지 확인해야한다. 그러나 노드가 갑자기 고장나면 쿠버네티스는 노드나 pod의 상태를 알 수 없다. 그럼 어떻게 확인해야 할까?
# 노드의 네트워크 어댑터 종료
$ gcloud compute ssh gke-kubia-default-pool-32a2cac8-m0g1
$ sudo ifconfig eth0 down
# node 상태 확인
$ kubectl get node
NAME STATUS AGE VERSION
gke-kubia-default-pool-32a2cac8-596v Ready 16m v1.6.2
gke-kubia-default-pool-32a2cac8-m0g1 NotReady 16m v1.6.2
# pod 상태 확인
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-0 1/1 Unknown 0 15m
kubia-1 1/1 Running 0 14m
노드가 지정 시간 내에(default 5min) 다시 온라인 상태가 되면 pod는 다시 running 상태가 된다. 시간을 초과한다면 마스터 노드에서 pod 리소스를 자동으로 종료 처리한다. $ kubectl describe po 확인
원래는 마스터 노드를 확인해서 kubelet이 pod를 종료해야하지만 연결이 안되어서 pod는 계속 실행되고있을 것이다.
# 일반적인 방법으로 pod 삭제
$ kubectl delete po kubia-0
$ kubectl get po
NAME READY STATUS RESTARTS AGE
kubia-0 1/1 Unknown 0 15m
kubia-1 1/1 Running 0 14m
pod가 삭제되려면 노드의 kubelet이 컨테이너가 종료되었음을 API 서버에게 알려야 하는데 네트워크가 끊겼으니 불가능해졌습니다.
$ kubectl delete po kubia-0 --force --grace-period 0
$ gcloud compute instances reset <node name>
출처
Kubernetes in Action
Q. headless 서비스를 생성할 때 작성해야하는 속성은 무엇일까요? 그리고 헤드리스 서비스가 무엇인지 간단하게 설명해주세요.
Q. pod가 실행되고 있는 node의 상태가 NotReady로 변하였습니다. 그 후로 5분 이상이 지나고도 node에 연결이 되지 않습니다. 이럴 때 어떤 방식으로 pod를 다시 재가동 시킬 수 있을까요? (pod-eviction-timeout 따로 설정한 적 없음)