=> 파드의 데이터는 언제든지 삭제 될 수 있다.
=> 볼륨을 이용해 파드가 삭제되어도 데이터를 영속적으로 남길 수 있다.
스테이트풀셋은 애플리케이션 구성(파드)을 복제하더라도, 스토리지 클래스를 이용해 파드가 필요로 하는 볼륨을 자동으로 프로비저닝하여 연결합니다.
따라서 첫번째 파드를 primary(master), 두번째 파드를 secondary 복제본처럼 구성하는 것도 가능하다.
동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다.
● 디플로이먼트
- 파드의 순서와 고유성을 보장하지 않는다.
- 파드를 상호 대체 가능한 리소스로 취급한다.
● 스테이트풀셋
- 파드의 순서와 고유성을 보장
- 파드를 상호 대체 할 수 없는 리소스로 취급한다.
Q.애플리케이션에 HTTP 500과 같은 에러가 발생한 경우, 컨테이너를 다시 실행해야 할 것 입니다. HTTP 에러가 발생했다는 것을 어떻게 알 수 있을까요? 어떻게 해야 쿠버네티스가 에러가 발생한 컨테이너를 자동으로 재시작하게 만들 수 있을까요?
Project2에서 ECS를 활용하면서 해당 기능을 사용한 적이 있다. 로드밸런서를 이용해 대상그룹 내에 포함된 컨테이너들의 연결 상태를 체크하는 Health Check 기능이었다.
Kubernetes에도 같은 기능을 하는 장치들이 있다.
Liveness Probe : 컨테이너의 활성 상태를 체크 (컨테이너의 동작을 체크한다.)
Readiness Probe : 서비스 가능 상태를 체크
(컨테이너가 요청을 처리할 준비가 되었는지 확인 한다.)
Startup Probe : 컨테이너 내 애플리케이션이 시작되었는지를 나타낸다.
위의 3가지 Probe들을 활용해 컨테이너 혹은 애플리케이션을 나의 서비스 환경에 맞게 상태 체크를 진행할 수 있다.
위의 세 가지 Probe들이 이용하는 상태 체크의 방식은 크게 3가지로 분류된다.
▶ Command Probe
<# Command Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec
spec:
containers:
- name: liveness
image: registry.k8s.io/busybox
imagePullPolicy: Always
ports:
- containerPort: 8080
<# livenessProbe 중 Command Probe 방법을 이용>
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
initialDelaySeconds: 5
periodSeconds: 5
initialDelaySeconds
: Kubelet에게 첫 번째 probe를 실행하기 전
대기 해야 할 초를 정의한다. (컨테이너 실행 후 대기시간)
PeriodSeconds
: Kubelet이 5초마다 LivenessProbe를 실행하도록 정의한다.
▶ Command Probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod liveness-exec
describe 명령어를 통해 pod의 상세정보를 확인하면 된다.
# 쿠버네티스 공식문서 기반 로그 예시
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 57s default-scheduler Successfully assigned default/liveness-exec to node01
Normal Pulling 55s kubelet, node01 Pulling image "registry.k8s.io/busybox"
Normal Pulled 53s kubelet, node01 Successfully pulled image "registry.k8s.io/busybox"
Normal Created 53s kubelet, node01 Created container liveness
Normal Started 53s kubelet, node01 Started container liveness
Warning Unhealthy 10s (x3 over 20s) kubelet, node01 Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
Normal Killing 10s kubelet, node01 Container liveness failed liveness probe, will be restarted
<# HTTP Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
labels:
test: liveness
name: liveness-http
spec:
containers:
- name: liveness
image: registry.k8s.io/liveness
args:
- /server
<# livenessProbe 중 HTTP Probe 방법을 이용>
livenessProbe:
httpGet:
path: /healthz
port: 8080
httpHeaders:
- name: Custom-Header
value: Awesome
initialDelaySeconds: 3
periodSeconds: 3
HTTP 요청의 경우 Kubelet에 내장된 함수를 이용해 GET 요청을 보내고 상태를 체크한다.
공식 문서에서 소개하는 healthz HandleFunc는 아래와 같다.
http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
duration := time.Now().Sub(started)
if duration.Seconds() > 10 {
w.WriteHeader(500)
w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
} else {
w.WriteHeader(200)
w.Write([]byte("ok"))
}
})
▶ HTTP probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod liveness-http
<# TCP Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
name: goproxy
labels:
app: goproxy
spec:
containers:
- name: goproxy
image: registry.k8s.io/goproxy:0.1
ports:
- containerPort: 8080
<# livenessProbe 중 TCP Probe 방법을 이용>
livenessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 15
periodSeconds: 20
▶ TCP Probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod goproxy
Probe의 사용이 왜 pod라는 kind에서 명세되어야 할까를 처음에는 고민되었다. 하지만 Probe의 목적을 생각하니 그 해답이 나왔다.
만약 Probe가 pod spec이 아닌 Service spec에 위치하게 된다, 그 서비스 타입을 load balacer로 가정하겠다.
Probe는 상태를 체크하기 위해 요청을 보낼 것이다. 결국 이것도 요청이라는 점이 이때 떠올랐다.
load balacer는 그 요청을 각 pod들에 무작위로 전달할 것이고 상태체크를 지속적으로 하기 때문에 결국에는 상태 확인을 할 수 있겠지만 결국 5개의 컨테이너의 상태 체크를 위해서는 요청이 5번 진행될때까지를 기다려야 한다는 점이었다.
그렇기 때문에 컨테이너의 갯수가 수십 수백개가 된다면 사실상 사용이 불가능한 수준에 이를 것이기에 Probe를 pod spec에 명시하지 않았을까 라고 생각하게 되었다.
Q. 왜 파드와 PV(퍼시스턴스볼륨)를 직접 연결하지 않는걸까요?
먼저 볼륨과 퍼시스턴스 볼륨에 대해 알아봤다.
▶ 볼륨과 퍼시스턴스 볼륨의 등장 배경?
파드는 Stateless한 특징을 가지고 있다. 즉 데이터의 소실 가능성을 언제든 가지고 있는 불완전한 서비스이다.
이 때문에 지속적으로 보관이 필요한 데이터의 소실 문제를 해결하기 위해 볼륨이 등장하게 되었다.
▶ 볼륨과 퍼시스턴스 볼륨의 공통점?
▶ 볼륨(Voulume)과 퍼시스턴스 볼륨(Persistent Voulume)의 차이점?
볼륨 (Voulume)
퍼시스턴스(Persistent Voulume)
▶볼륨에서 반드시 알아야 할 퍼시스턴스 볼륨 클레임(Persistent Voulume Claim)의 개념
쿠버네티스 공식문서에서 PVC는 사용자의 스토리지에 대한 요청이라고 나와 있다. 해당 요청을 통해 PV의 특정 크기 및 접근 모드 등을 요청할 수 있다.
PVC에는 두 가지의 프로비저닝이 존재하는데
정적 프로비저닝의 경우
클러스터 관리자가 여러 PV를 만들고 각 개발자에게 실제 스토리지의 세부 사항을 제공하여 요청을 받는 형태이다.
동적 프로비저닝의 경우
스토리지클래스를 기반으로 관리자가 생성한 PV가 사용자의 PVC와 일치하지 않으면 요청된 PVC에 맞춰 PV를 동적으로 프로비저닝 하려고 시도하는 형태이다.
지금까지는 위의 질문에 대한 기본 개념이었고 아래부터가 해답이다.
나는 이 해답을 두 개로 정리했다.
Pod와 PV를 직접 연결하게 된다면 각 개발자가 PV를 Pod에 mount함으로써 PV 규격의 통일성을 해치고 IaC를 통해 관리하는 쿠버네티스의 장점을 소실하게 된다.
하지만 PVC를 통해 연결한다면 각 개발자는 PV를 ‘mount’하는 것이 아니라 ‘요청’할 수 있게 되고 어떤 PV를 연결시켜 줄지에 대한 권한은 클러스터 관리자인 DevOps에게로 이전된다.
이렇게 PVC를 통해 요청에 맞는 적절한 PV를 연결시켜 줌으로써 각 개발자는 PV를 Pod에 마운트한 것처럼 사용할 수 있고
DevOps는 각 PV를 리소스로써 활용하면서 IaC를 통해 관리하는 장점을 유지하면서도 원활한 서비스를 구성할 수 있다고 생각한다.
PV와 포드를 분리하는 이유는 Kubernetes 워크로드의 유연성, 이식성 및 내구성을 향상시키기 때문이다.
워크로드의 유연성이란?
PV가 필요한 상태 저장 애플리케이션이 있는 경우 PV를 포드에서 분리하고 PVC를 통해 포드와 연결함으로써 상황, 환경에 맞는 PV를 적절히 선택하여 사용할 수 있고 반드시 고정된 PV에 포드에 데이터가 저장되어지지 않아도 된다.
워크로드의 이식성이란?
애플리케이션을 수평으로 확장해야 하는 경우 더 많은 포드를 생성하고 이를 PVC를 통해 포드와 연결시켜줌으로써 데이터가 모든 포드 간에 공유 될 수 있다.
워크로드의 내구성이란?
PV의 경우 포드가 삭제된 후에도 지속되도록 설계되어 있다. PVC를 이용할 경우 의존성까지 줄일 수 있게 되어 컴퓨팅 리소스와 스토리지 리소스간에 영향을 미치지 않고 독립적으로 확장 및 변경이 가능하다.
아래는 PVC를 통해 포드와 PV를 연결하는 매니패스트 구성이다.
쿠버네티스 공식 문서에 따르면 아래 3가지 순서대로 진행하라고 되어 있다.
# 1. PV 생성
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity: # 크기
storage: 1Gi
accessModes:
- ReadWriteOnce # 하나의 PVC만 읽거나 쓴다
- ReadOnlyMany # 여러 PVC가 읽기만 한다
persistentVolumeReclaimPolicy: Retain # PVC 와 연결이 해제되면 지우거나 삭제하지 않고 유지한다
hostPath:
path: /tmp/mongodb
# 2. PV에 바인딩되는 PVC 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi #스토리지 사이즈
accessModes: # 연결할 PV는 아래 옵션으로 접근이 가능해야 한다. (Label을 맞추는 것과 동일한 조건의 개념으로 이해했다)
- ReadWriteOnce
storageClassName: "" # 동적 프로비저닝을 위해 사용
# 3. PVC를 이용하는 Pod 생성
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc # PVC 의 이름