▶︎ Storage Volume
쿠버네티스는 Storage Volune을 정의하는 방법으로 이 기능을 제공하고 있다. Storage Volume은 Pod와 같은 최상위 리소스는 아니지만 Pod의 일부분으로 정의되며 Pod와 동일한 lifecycle을 가진다.
즉, Pod가 시작되면 Volume이 생성되고, 파드가 삭제되면 볼륨이 삭제된다는 뜻이다.
이를 통해 생성되는 새로운 컨테이너는 이전 컨테이너가 볼륨에 기록한 모든 파일들을 열람할 수 있다. 또한 파드가 여러 개의 컨테이너를 가진 경우 모든 컨테이너가 볼륨을 공유할 수 있게된다.
▶︎ 요약
- 쿠버네티스 Volume은 Pod의 구성 요소로 컨테이너와 동일하게 Pod Spec에서 정의된다.
- 볼륨은 쿠버네티스 오브젝트가 아니므로 자체적으로 생성, 삭제될 수 없다.
- 볼륨은 Pod의 모든 컨테이너에서 사용 가능하지만 접근하려는 컨테이너에서 각각 마운트 되어야 한다. 각 컨테이너에서 파이시스템의 어느 경로에나 볼륨을 마운트할 수 있다.
▶︎ 로컬 볼륨
자중 사용되는 Volume 종류는 아님.
- emptyDir: ephemeral Data를 저장하는데 사용되는 간단한 empty 디렉터리이다.
- hostPath: Node 파일시스템을 Pod의 디렉터리로 마운트하는데 사용한다.
‣ emptyDir
• 특징
- Pod의 컨테이너 간 Volume을 공유하기 위해 사용하며 컨테이너 간 파일을 공유할 때 유용
- Pod가 실행되는 도중에만 필요한 휘발성 데이터를 각 컨테이너가 함께 사용할 수 있도록 임시 저장 공간을 생성함
• 공유 시나리오
- emptydir-pod YAML 파일 생성 (컨테이너 1, 컨테이너 2)
- 컨테이너 1 (content-creator)에 /data 디렉토리에 test.html 작성
- 컨테이너 2 (apache-webserver) 에서의 /usr/local/apache2/htdocs/ 디렉토리와 컨테이너 1의 /data Volume과 공유된다.
즉, 컨테이너 2의 /usr/local/apache2/htdocs/ 에 test.html 이 저장되었다고 볼 수 있으며 Pod IP 주소를 호출시 /htdocs/test.html 파일을 불러들인다.
emptyDir-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: emptydir-pod
spec:
containers:
# 첫 컨테이너는 content-creator 이라고 이름 짓고 alicek106/alpine-wget:latest 이미지를 실행
- name: content-creator
image: alicek106/alpine-wget:latest
args: ["tail", "-f", "/dev/null"]
# my-emptydir-volume 이라는 볼륨을 컨테이너의 /data 에 마운트한다
# 마운트 = 디스크 파티션을 특정한 위치(디렉토리)에 연결시켜주는 과정
volumeMounts:
- name: my-emptydir-volume
mountPath: /data # 1. 이 컨테이너가 /data 에 파일을 생성하면
# my-emptydir-volume이라는 볼륨을 컨테이너의 /usr/local/apache2/htdocs/에 마운트한다
- name: apache-webserver
image: httpd:2
volumeMounts:
- name: my-emptydir-volume
mountPath: /usr/local/apache2/htdocs/ # 2. 아파치 웹 서버에서 접근 가능합니다.
# my-emptydir-volume 이란 단일 볼륨을 위의 컨테이너 2개에 마운트한다.
volumes:
- name: my-emptydir-volume
emptyDir: {} # 포드 내에서 파일을 공유하는 emptyDir
• 공유 테스트
$ kubectl apply -f emptydir-pod.yaml
pod/emptydir-pod created
# content-creater 컨테이너에 접속하여 test.html 작성
$ kubectl exec -it emptydir-pod -c content-creator sh
# echo Hello, Kubernetes! >> /data/test.html
# emptydir-pod IP 주소 확인
$ kubectl describe pod emptydir-pod | grep IP
cni.projectcalico.org/podIP: 172.16.197.4/32
cni.projectcalico.org/podIPs: 172.16.197.4/32
IP: 172.16.197.4
# 임시 Pod를 생성하여 정상적으로 emptydir Volume이 공유되고 있는지 확인
$ kubectl run -i --tty --rm debug --image=alicek106/ubuntu:curl --restart=Never -- curl 172.16.197.4/test.html
Hello, Kubernetes!
‣ HostPath
• 특징
gitRepo나 emptyDir Volume은 Pod가 종료되면 데이터도 같이 삭제되면 반면 HostPath Volume은 그렇지 않다.
HostPath Volume은 호스트의 디렉토리(Node 단위)를 Pod와 공유하여 데이터를 저장한다.
hostpath-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
spec:
# my-container이라는 이름의 컨테이너에 /etc/data에 my-hostpath-volume 의 볼륨을 마운트
containers:
- name: my-container
image: busybox
args: [ "tail", "-f", "/dev/null" ]
volumeMounts:
- name: my-hostpath-volume
mountPath: /etc/data
# /tmp 경로에 마운트
volumes:
- name: my-hostpath-volume
hostPath:
path: /tmp
• 공유 테스트
# 컨테이너 內 /etc/data 디렉터리에 mydata 이름의 파일 생성
$ kubectl exec -it hostpath-pod -- touch /etc/data/mydata
# Pod가 생성된 Workernode로 접속
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hostpath-pod 1/1 Running 0 17m 172.16.46.7 k8s-w2 <none> <none>
# 노드에 파일 생성 확인
$ ls /tmp/mydata
/tmp/mydata
• 적합성
HostPath Volume은 데이터 저장소로 채택하기에는 적합하지 않음.
- 특정 노드의 FileSystem에 저장되므로 파드가 다른 노드로 Scheduling이 되면 더 이상 이전 데이터륿 볼 수 없음.
- k8s-node1 노드에 HostPath를 지정됐는데 파드가 k8s-node2에 스케줄링 되면 더 이상 이전 데이터를 불러올 수 없게 되는 것
- Pod가 어떤 노드에 Scheduling 되느냐에 따라 민감하기 때문에 일반적인 Pod에 사용하는 것은 좋은 생각이 아니다. 특수한 경우를 제외한다면 hostPath를 사용하는 것은 보안 및 활용성 측면에서 그다지 바람직하지 않다.
▶︎ 네트워크 볼륨
네트워크 볼륨부터 **PersistentVolume(=PV)와 PersistentVolumeClaim(=PVC)**라는 개념이 존재한다.
- Pod 내부에서 특정 데이터를 보유해야 하는 Stateful한(DB) Application 경우 Stateless한 (Pod, Deployment) 데이터를 영속적으로 저장하기 위한 방법이 필요하다.
- Pod에서 실행중인 애플리케이션이 디스크에 데이터를 유지해야 하고 Pod가 다른 노드로 ReScheduling된 경우에도 동일한 데이터를 사용해야 한다면 Local Volume (emptyDir, HostPath Volume)을 사용할 수 없다.
어떤 클러스터 노드에서도 접근이 필요할 수 있기에 NAS 유형의 스토리지에 저장이 되어야만 함.
On-Premise 환경에서도 구축할 수 있는 NFS, ISCSI, GlusterFS와 같은 Network Volume뿐만 아니라 AWS의 EBS(Elastic Block Store) 등과 같은 클라우드 플랫폼의 Volume의 Pod에 마운트할 수 있다.
‣ NFS를 네트워크 볼륨으로 사용
Deployment 및 Service 생성
쿠버네티스 클러스터 내부에서 임시 NFS 서버를 생성하자.
NFS 기능을 간단히 이용해보기 위한 목적이므로 Deployment와 Service를 생성해주어야 한다.
nfs-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-server
spec:
selector:
matchLabels:
role: nfs-server
template:
metadata:
labels:
role: nfs-server
spec:
containers:
- name: nfs-server
image: gcr.io/google_containers/volume-nfs:0.8
ports:
- name: nfs
containerPort: 2049
- name: mountd
containerPort: 20048
- name: rpcbind
containerPort: 111
securityContext:
privileged: true
nfs-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: nfs-service
spec:
selector:
role: nfs-server
ports:
- name: nfs
port: 2049
- name: mountd
port: 20048
- name: rpcbind
port: 111
$ kubectl apply -f nfs-deployment.yaml
$ kubectl apply -f nfs-svc.yaml
‣ Pod 생성
Deployment와 Service를 통해 임시 NFS 서버를 생성하였다면 이제 NFS 서버의 볼륨을 Pod에서 마운트해보자.
nfs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod
spec:
containers:
- name: nfs-mount-container
image: busybox
args: [ "tail", "-f", "/dev/null" ]
volumeMounts:
- name: nfs-volume
mountPath: /mnt # 포드 컨테이너 내부의 /mnt 디렉터리에 마운트
volumes:
- name : nfs-volume
nfs: # NFS 서버의 볼륨을 포드의 컨테이너에 마운트
path: /
server: {NFS_SERVICE_IP}
YAML 파일에서 유의해야할 부분은 {NFS_SERVICE_IP}이다.
- NFS Volume의 마운트는 컨테이너 내부가 아닌 Worker Node에서 발생하기에 서비스의 DNS 이름으로 NFS 서버에 접근이 불가능하다.
- 노드 내부에서는 Pod의 IP로 통신이 가능하지만, 쿠버네티스의 DNS 사용하도록 설정 되어있지는 않다.
방법은 여러가지가 있지만 여기서는 NFS 서비스의 ClusterIP를 직접 얻은 뒤, YAML 파일에 사용하는 방식으로 Pod를 생성해보겠다.
# NFS 서버에 접근하기 위한 서비스의 Cluster IP를 획득
$ export NFS_CLUSTER_IP=$(kubectl get svc/nfs-service -o jsonpath='{.spec.clusterIP}')
# nfs-pod의 Server 항목을 NFS_CLUSTER_IP로 교체하여 생성
$ cat ./nfs-pod.yaml | sed "s/{NFS_SERVICE_IP}/${NFS_CLUSTER_IP}/g" | kubectl apply -f -
💡 kubectl get 명령어의 -o jsonpath는 Resource의 특정 정보만 가져올 수 있는 편리한 옵션이다.
• NFS 마운트 테스트
NFS서버가 /mnt 디렉터리에 마운트 되어 있음을 확인할 수 있다.
네트워크로 접근할 수 있는 Volume인 NFS 서버에 파일이 영속적으로 저장되며 컨테이너 내부의 /mnt 디렉터리에 저장된 파일은 Pod가 다른 node로 옮겨지거나 Pod를 재시작해도 삭제되지 않는다.
# nfs-pod 접속
$ kubectl exec -it nfs-pod -- sh
/ # df -h
Filesystem Size Used Available Use% Mounted on
...
10.99.241.166:/ 38.7G 4.0G 34.7G 10% /mnt
...
• 트러블슈팅
- Error: NFS 파드 생성 중 ContainerCreating 상태에서 안 넘어가는 경우
$ kubectl get pod nfs-pod
NAME READY STATUS RESTARTS AGE
nfs-pod 0/1 ContainerCreating 0 16m
$ kubectl describe po nfs-pod
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 30s default-scheduler Successfully assigned default/nfs-pod to k8s-w2
Warning FailedMount 14s (x6 over 30s) kubelet MountVolume.SetUp failed for volume "nfs-volume" : mount failed: exit status 32
---
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-pod 0/1 ContainerCreating 0 9s <none> k8s-w2 <none> <none>
nfs-server-ddf754fc-9vwsn 1/1 Running 0 55m 172.16.197.5 k8s-w3 <none> <none>
# nfs-pod가 할당된 노드에 접속하여 NFS 패키지 설치
$ sudo apt-get install nfs-common -y
# Pod 재확인
$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-pod 1/1 Running 0 79s 172.16.46.8 k8s-w2 <none> <none>
nfs-server-ddf754fc-9vwsn 1/1 Running 0 57m 172.16.197.5 k8s-w3 <none> <none>