컨테이너는 이미지가 곧 컨테이너이다. 컨테이너의 생명 주기는 이미지와 항상 똑같다. 이를 불변의 인프라라고 한다. 하지만, 운영체제가 있다면 생명 주기가 똑같을 수 없다. 이를 가변의 인프라라고 한다. 이렇게 서로 다른 리소스들의 생명 주기를 분리하기 위해 필요한 것이 볼륨이다.
볼륨으로 여러 스토리지 중 하나를 선택할 수 있다.
emptyDir은 임시 디렉터리를 파드의 라이프타임동안 공유한다. 볼륨은 파드와 볼륨을 별로의 라이프사이클을 가지고 갈 수 있도로 만든 것이다. 하지만 emptyDir은 예외적으로 파드의 라이프사이클과 함께 한다 임시 디렉터리를 호스트에 만들어, 파드가 쓸 수 있도록 전해 준다.
emptyDir 볼륨은 파드가 노드에 할당될 때 처음 생성되며, 해당 노드에서 파드가 실행되는 동안에만 존재한다. 이름에서 알 수 있듯이 emptyDir 볼륨은 처음에는 비어있다. 파드 내 모든 컨테이너는 emptyDir 볼륨에서 동일한 파일을 읽고 쓸 수 있지만, 해당 볼륨은 각각의 컨테이너에서 동일하거나 다른 경로에 마운트될 수 있다. 어떤 이유로든 노드에서 파드가 제거되면 emptyDir 의 데이터가 영구적으로 삭제된다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs-fortune
spec:
replicas: 1
selector:
matchLabels:
app: myapp-rs-fortune
template:
metadata:
labels:
app: myapp-rs-fortune
spec:
containers: #파드의 스펙
- name: web-server
image: nginx:alpine
volumeMounts: #볼륨과 서비스를 마운트한다.
- name: web-fortune
mountPath: /usr/share/nginx/html #볼륨을 /usr/share/nginx/html에 마운트한다.
# nginx의 document route
readOnly: true
ports:
- containerPort: 80
- name: html-generator
image: ghcr.io/c1t1d0s7/fortune
volumeMounts:
- name: web-fortune
mountPath: /var/htdocs #같은 볼륨을 다른 위치에 마운트하고 있다
volumes:
- name: web-fortune #이름은 필수
emptyDir: {}
두 개의 컨테이너가 같은 볼륨에 연결되어 있다. html-generator에는 fortune이라는 도구가 있다. 그 도구가 /var/htdocs에 index.html 파일을 생성하고 3초마다 갱신한다. 그것이 볼륨에 저장되면, 같은 볼륨으로 마운트되어 있는 /usr/share/nginx/html에도 index.html 파일이 생긴다.
동일한 파드 내에 여러 컨테이너가 존재하는 경우 -c 옵션을 통해 구분해 줘야 한다.
kubectl exec myapp-rs-fortune-w9wz2 --sh #오류남
kubectl logs myapp-rs-fortune-w9wz2 -it -c web-server -- sh
kubectl logs myapp-rs-fortune-w9wz2 -it -c html-generator -- sh
fields로는 medium과 sizeLimit이 있다.
medium
매체. 디폴트 값은 비워져 있다. 임시 디렉터리를 생성할 매체를 설정한다. 기본 설정은 파드가 뜨는 호스트를 사용한다. 메모리로 설정하면 디스크를 쓰는 것이 아니라 메모리를 사용한다. 이것을 램 디스크(tmpfs) 라고 한다. tmpfs는 매우 빠르다. 하지만 노드를 재부팅하면 tmpfs가 휘발되고, 작성하는 모든 파일이 컨테이너 메모리 제한에 포함된다. 일반적으로는 디스크를 사용한다.
sizeLimit
용량을 지정한다.
컨테이너와 볼륨은 같은 들여쓰기 레벨을 가지고 있다.
더 이상 사용되지 않는다. 기본적으로 EmptyDir을 사용한다. git 저장소에서 코드를 가지고 와 EmptyDir 형태로 제공한다.
gitRepo는 더이상 쓰이지 않지만, git 저장소와 동기화시켜 볼륨을 제공하는 것은 매우 많이 사용되는 형태이다. 이를 위한 대안이 아래의 코드이다.
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod-git
spec:
initContainers: #초기화 컨테이너
- name: git-clone
image: alpine/git
args: #cmd 대체
- clone #git 명령어는 컨테이너의 엔트리포인트에 붙어있다.
- --single-branch
- --
- https://github.com/kubernetes/kubernetes
- /repo
volumeMounts:
- name: git-repository
mountPath: /repo
containers:
- name: git-container
image: busybox
args: ['tail', '-f', '/dev/null'] #컨테이너를 죽이지 않기 위한 임시 조치
volumeMounts:
- name: git-repository
mountPath: /repo
volumes:
- name: git-repository
emptyDir: {}
볼륨에 초기화 컨테이너가 데이터를 채워 놓고 종료된 후, 원래 컨테이너가 죽지 않고 계속해서 실행된다.
초기화 컨테이너가 실행되면 STATUS에 Init 상태로 표시된다.
쿠버네티스 소스코드가 받아져 있는 것을 확인할 수 있다.
hostPath 볼륨은 호스트 노드의 파일시스템에 있는 파일이나 디렉터리를 파드에 마운트 한다. 이것은 대부분의 파드들이 필요한 것은 아니지만, 일부 애플리케이션에 강력한 탈출구를 제공한다.
예를 들어, hostPath 의 일부 용도는 다음과 같다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs-hp
spec:
replicas: 2
selector:
matchLabels:
app: myapp-rs-hp
template:
metadata:
labels:
app: myapp-rs-hp
spec:
#nodeName: kube-node1 #파드를 특정한 노드(이 경우에는 노드1)에 배치한다.
containers:
- name: web-server
image: nginx:alpine
volumeMounts:
- name: web-content
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: web-content
hostPath:
type: Directory
path: /srv/web_contents
node2, node3 모두에 만들어진다. 하지만 서로 연결되지는 않는다. 네트워크 기반의 스토리지가 아니기 때문이다. (emptyDir도 동일) 그러므로 네트워크를 넘어서 있는 볼륨을 지정할 수는 없다. 따라서, node2와 node3는 각자 host에서 web_contents를 찾는데 host에 없으므로 마운트할 수 없다. 따라서, node2와 node3의 컨테이너는 작동하지 않는다.
스토리지를 host에 만들어 다른 내용을 저장하면 각각 저장한 내용으로 나온다.
네트워크 기반의 스토리지는 많지만, 대표적인 네트워크 기반의 스토리지는 nfs이다. nfs 볼륨을 사용하면 기존 NFS (네트워크 파일 시스템) 볼륨을 파드에 마운트 할수 있다. 파드를 제거할 때 지워지는 emptyDir 와는 다르게 nfs 볼륨의 내용은 유지되고, 볼륨은 그저 마운트 해제만 된다. 이 의미는 NFS 볼륨에 데이터를 미리 채울 수 있으며, 파드 간에 데이터를 공유할 수 있다는 뜻이다. NFS는 여러 작성자가 동시에 마운트할 수 있다. NFS 전용 서버를 만들어 관리하는 경우가 많다.
sudo apt install nfs-kernel-server -y #설치
sudo mkdir /srv/nfs-volume
echo "/srv/nfs-volume *(rw,sync,no_subtree_check,no_root_squash)"
| sudo tee /etc/exports/srv/nfs-volume *(rw,sync,no_subtree_check,no_root_squash)
cat /etc/exports
/srv/nfs-volume *(rw,sync,no_subtree_check,no_root_squash)
sudo exportfs-arv
exporting *:/srv/nfs-volume
컨트롤 플레인에서 각각의 노드의 파드에 마운트하는 것이 아니라 노드로 마운팅하는 것이다. 따라서, 파드가 아니라 노드가 마운팅 할 수 있어야 한다.
ansible kube_node -i ~/kubespray/inventory/mycluster/inventory.ini -m apt
-a 'name=nfs-common' --become
영구적인 볼륨이라는 뜻이다. emptyDir과 hostPath로 사용이 가능하다. 여태껏 배웠던 볼륨의 종류들이 볼륨에 대한 설정을 여태껏 파드 내에서 해 와 파드가 삭제되면 볼륨도 삭제되었다면, 퍼시스턴트 볼륨을 사용하면 볼륨을 별도로 사용할 수 있다. 이는 분류된 리소스이다.
퍼시스턴트 볼륨은 볼륨을 파드에 직접 연결하지 않고 실제 스토리지와 연결한다. PV가 실제 스토리지를 정의하여 관리자가 관리하고, 개발자는 추상화되어 파드와 연결된 PV를 요청한다. 이렇게 PV를 요청하는 것을 PVC(Persistent Volume Claim)이라 한다.
PV와 PVC의 생명 주기는 다음과 같다.
프로비저닝
PV와 PVC를 생성한다.
바인딩
PVC를 생성하여 PV와 연결한다. PVC는 파드와 연결되어 있고, PV는 스토리지와 연결되어 있어야 한다.
사용
파드가 해당 스토리지를사용할 수 있는 단계가 된다.
반환
볼륨을 다 사용한 후 리소스를 반환할 수 있는 API를 사용하여 PVC 오브젝트를 삭제한다.
PV는 스토리지는 저장하는 소스이고, PV를 프로비저닝 할 수 있는 방법은 두 가지이다: 정적 프로비저닝과 동적 프로비저닝이다. 결론적으로 얘기하자면, 동적 프로비저닝이 선호된다. 정적 프로비저닝은 PV(스토리지)를 수동으로 생성하고 삭제하는 것이고, 동적 프로비저닝은 자동으로 삭제하는 것이다.
cat myapp-pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: myapp-pv-nfs
spec:
capacity:
storage: 1Gi #스토리지의 용량
accessModes: #접근 제어
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
nfs: #스토리지는 nfs로 사용
path: /srv/nfs-volume
server: 192.168.56.11
capacity
nfs 스토리지는 용량을 지정할 수 없지만, storage 옵션이 없으면 만들어지지 않기 때문에 지정해 줘야 한다.
accessMode
PV를 선언 할 때 무조건 설정해줘야 한다. 여러 개를 설정할 수도 있다. 하지만, 스토리지에 따라서 read와 write가 될 수도 있고 안 될수도 있기 때문에 확인해야 한다. 리소스 제공자가 지원하는 방식으로 호스트에 퍼시스턴트볼륨을 마운트할 수 있다. 볼륨 제공자들은 서로 다른 기능을 가지며 각 PV의 접근 모드는 해당 볼륨에서 지원하는 특정 모드로 설정된다. 예를 들어 NFS는 다중 읽기/쓰기 클라이언트를 지원할 수 있지만 특정 NFS PV는 서버에서 읽기 전용으로 export할 수 있다. 각 PV는 특정 PV의 기능을 설명하는 자체 접근 모드 셋을 갖는다.
ReadWriteOnce
하나의 노드에서 해당 볼륨이 읽기-쓰기로 마운트 될 수 있다.
ReadWriteOnce
접근 모드에서도 파드가 동일 노드에서 구동되는 경우에는 복수의 파드에서 볼륨에 접근할 수 있다.
ReadOnlyMany
볼륨이 다수의 노드에서 읽기 전용으로 마운트 될 수 있다.
ReadWriteMany
볼륨이 다수의 노드에서 읽기-쓰기로 마운트 될 수 있다. 모든 노드에서 사용할 수 있는 것은 아니다.
ReadWriteOncePod
볼륨이 단일 파드에서 읽기-쓰기로 마운트될 수 있다. 전체 클러스터에서 단 하나의 파드만 해당 PVC를 읽거나 쓸 수 있어야하는 경우 ReadWriteOncePod 접근 모드를 사용한다. 이 기능은 CSI 볼륨과 쿠버네티스 버전 1.22+ 에서만 지원된다.
cat myapp-pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc-nfs
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
volumeName: myapp-pv-nfs #PV의 이름으로 지정한다.
storageClassName: '' #동적 프로비저닝에서 사용한다.
cat myapp-rs-nfs.yaml
apiVersion: apps/v1
kind: ReplicaSet
meatadata:
name: myapp-rs-nfs
spec:
replicas: 2
selector:
matchLabels:
app: myapp-rs-nfs
template:
metadata:
labels:
app: myapp-rs-nfs
spec:
containers:
- name: myapp
image: nginx
volumeMounts:
- name: nfs-share
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: nfs-share
persistentVolumeClaim: #파드와 PVC를 연결
claimName: myapp-pvc-nfs
cat myapp-svc-nfs.yaml
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-nfs
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: myapp-rs-nfs
cd /srv/nfs-vloume/
echo "hello static nfs" | sudo tee /srv/nfs-vloume/index.html #공유할 파일 생성
kubectl create -f myapp-pv-nfs.yaml
kubectl create -f myapp-pvc-nfs.yaml
kubectl create -f myapp-rs-nfs.yaml
kubectl create -f myapp-svc-nfs.yaml
파드를 삭제하고 PVC를 삭제하면 PV의 STATUS가 Released 상태로 바뀐다. 다시 PVC를 만들어도 PV에 연결이 되지 않고 PV는 Released 상태로 남아 있다. PVC는 PV와 연결되지 않고 Pending 상태에서 멈춘다. 이렇게 PV를 직접 만드는 것을 정적 프로비저닝이라고 할 수 있다.
PVC 오브젝트를 삭제한 후 볼륨에 수행할 작업을 클러스터에 알려준다. 우선 PVC와 PV를 지우기 위해선 파드를 삭제해야 한다. PVC에는 정보가 저장되어 있지 않지만, PVC를 지웠을 때 PV의 처리를 어떻게 할지 정하는 것이 반환이다. 보존하거나, 삭제하거나, 재활용할 수 있다.
재사용하기 위해선 사실 PVC를 다른 파드에 연결하면 된다. 스케일링하면 자동으로 파드에 PVC가 붙는다. 결국 반환이라는 과정은 더 이상 데이터의 접근이 필요하지 않을 때 수행한다.
PV를 지워버리면 데이터를 재사용할 수 없으므로, 재사용을 위해 PV를 남겨둔다고 생각할 수 있다. 하지만, PV는 재사용이 불가능하다. PVC와 PV가 바인딩된 후 바인딩을 끊어버리면(PVC를 삭제하면) PV는 재사용이 불가능하다. 결국, 볼륨을 수동으로 반환(삭제)해야 한다. 이럴 때 사용하는 것이 보존 정책이다.
PV를 수동으로 순서는 다음과 같다.
Delete 반환 정책을 지원하는 볼륨 플러그인의 경우, 삭제는 쿠버네티스에서 퍼시스턴트볼륨 오브젝트와 외부 인프라(예: AWS EBS, GCE PD, Azure Disk 또는 Cinder 볼륨)의 관련 스토리지 자산을 모두 삭제한다. 대부분의 기본 정책이다.
Recycle 정책은 다시 사용하지 않는다. PV를 재사용 가능하게끔 세팅하는 것이긴 하지만, PV를 재사용 한다는 것은 스토리지를 초기화한다는 것이고, 이것은 데이터를 모두 삭제하고 새롭게 만든다는 것이다. 스토리지의 데이터를 모두 삭제하는 방법은 스토리지마다 다르다. 쿠버네티스 입장에선 모든 스토리지의 삭제 정책을 지원할 필요가 없으므로 더 이상 사용하지 않는다. 대신 동적 프로비저닝의 사용을 권장한다.
이렇게 정적 프로비저닝은 PV를 하나하나 만들어 줘야 하고 직접 요청해 줘야 한다. 이 과정을 편하게 할 수 있도록 만든 것이 바로 동적 프로비저닝이다.
PVC는 스토리지 클래스를 요청해야 한다. storageClassName을 요청해 줘야 한다는 뜻이다.
스토리지 클래스는 PV를 만들기 위한 프로파일이다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard
provisioner: kubernetes.io/aws-ebs #aws-ebs를 사용할 때의 프로비저너
parameters:
type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
- debug
volumeBindingMode: Immediate
쿠버네티스에는 NFS를 위한 프로바이저가 내장되어 있지 않으므로, NFS를 위한 스토리지 클래스를 생성하기 위해선 외부 프로바이저를 이용해야 한다. 프로바이저
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
cd nfs-subdir-external-provisioner/deploy/
vi deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.56.11 #nfs IP로 지정
- name: NFS_PATH
value: /srv/nfs-volume #nfs 경로로 지정
volumes:
- name: nfs-client-root
nfs:
server: 192.168.56.11 #nfs IP로 지정
path: /srv/nfs-volume #nfs 경로로 지정
kubectl apply -k . #현재 디렉토리의 kustomization.yaml을 순서대로 실행
cat myapp-rs-dynamic.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs-dynamic
spec:
replicas: 2
selector:
matchLabels:
app: myapp-rs-dynamic
template:
metadata:
labels:
app: myapp-rs-dynamic
spec:
containers:
- name: web-server
image: nginx:alpine
volumeMounts:
- name: web-content
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
volumes:
- name: web-content
persistentVolumeClaim:
claimName: myapp-pvc-dynamic #동적 프로비저닝
cat myapp-pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc-dynamic
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-client #동적 프로비저닝
kubectl create -f myapp-rs-dynamic.yaml
자동으로 PV가 생성되어 있는 것을 확인할 수 있다. 이 상태에서 PVC를 지우면 PV도 삭제된다.