kube 를 통해 띄워진 Container 에서 생성한 데이터들은

(Docker 에서 그러했듯이) 어딘가에 저장하지 않으면 Container 가 제거되거나, 확장되는 등

이벤트가 발생했을 때 데이터를 잃어버릴 수 있음

따라서 kube 에서 데이터를 유지할 수 있는 방법인 Volume 에 대해 알아봄

 

deployment 와 service 가 함께 이미 실행되고 있는 상황을 전제로 설명 이어감

 

 


 

 

 

deployment 에 의해 생성된 (pod를 포함하는) container 가 crash 충돌 등에 의해 재시작하게 되면,

pod 에 의해 저장하고 있던 데이터는 모두 날아가게 됨

이를 방지하기 위해 volume 기능을 사용하여 데이터를 기존과 다른 어느 외부에 지정

 

[volume types documentation]

kube 는 다양한 volume 및 driver 를 지원한다고 함

kube 는 다양한 환경(Cloud provider (AWS, Google, Azure 등) , IDC 등)위에서 운영될 수 있기 때문에

이에 맞는 volume 과 driver 를 제공하는 거라고 함

 

 volume 은 pods 의 spec 부분에 추가하여 설정 가능함

아래와 같이 pods spec (in deployment yaml) 을 설정하면 됨

 


 

 

emptyDir 타입의 volume 이 적용된 yaml 예제를 들어 설명함

 

....
    spec:
      containers:
        - name: demo
          image: demo
          volumeMounts:
            - mountPath: /app/demo  #Container 내부 경로
              name: demo-volume

      volumes:
        - name: demo-volume
          emptyDir: {}

 

 

여기서 사용된 emptyDir 타입의 volume

pod 가 시작 될 때마다 단순히 새로운 빈 dir 를 생성

(여기서 말하는 빈 dir 의 path 는 mountPath 로 설정한 위치가 됨)

pod 가 살아있는 동안 그 dir 를 활성 상태로 유지하고, pod 의 데이터를 그 dir 에 채움

container 가 재시작되거나 심지어 제거되더라도 해당 dir 에 들어있는 데이터는 유지됨

하지만 dir 은 pod 와 연결되어있기 때문에 pod 가 제거되면 dir 도 같이 제거됨(데이터도 함께 삭제) 

pod 가 제거된 이후, 다시 생성되면 volume 에 의해 새로운 빈 dir 가 생성됨(데이터 돌아오지 않음)

 

containers.volumeMounts.name 에 설정된 volume명(위 예에서 demo-volume) 과

volumes.name 에 설정된 volume명이 동일한 것끼리 서로 매칭(mount)됨

즉, 위 예제에서는 emptyDir 타입을 갖는 demo-volume 이 container 에 mount 됨

 

emptyDir 타입의 volume 은 하나의 pod 에 연결되어있음

만약 pod가 2대 이상인 경우라면 아래와 같은 상황이 연출됨

- podA, podB 가 존재

- podA 의 emptyDir volume 에 데이터가 저장됨

- podA 의 emptyDir volume 에서 데이터를 읽어보니 잘 읽힘

- podA 의 container 가 죽음

- (loadBalancer 에 의해 트래픽이 podB 로 이동) podB 의 emptyDir volume 에서
  데이터를 읽어보니 실패함(데이터가 없어서)

- podA 가 다시 살아남

- podA 의 emptyDir volume 에서 데이터를 읽어보니 잘 읽힘

 

위와 같은 상황을 해소하기 위해선 pod 당 새로운 빈 dir 를 생성하는 것으로 전환해야 함

그리고 그것을 도와주는 것이 hostPath driver

 

 

 


 

 

[hostPath driver documentation]

hostPath driver 를 통해 호스트 머신(=node) 의 path 를

(각 Pod 를 실행하는 실제 머신에 연결되도록) 설정할 수 있음

설정한 hostPath 위치 내 데이터는 각각의 pod 에 연결됨

그래서 2대 이상의 pods 가 존재해도,

여러 pods 가 (pod 의 특정 경로 대신) 하나의 호스트 머신 위의 동일한 dir 를 공유할 수 있음

(물론 이는 동일한 pod 에서 모든 요청을 처리하는 경우에만 유용함)

 

hostPath 는 아래와 같이 deployment 의 yaml 내 pod spec 설정 부분에 추가 설정함

 

....
    spec:
      containers:
        - name: demo
          image: demo
          volumeMounts:
            - mountPath: /app/demo  #Container 내부 경로
              name: demo-volume

      volumes:
        - name: demo-volume
          hostPath:
            path: /data  #외부 경로
            type: DirectoryOrCreate

 

 

hostPath.path 에 경로를 설정

hostPath 는 항상 빈 dir 를 생성하지 않으며, 이미 존재하는 경로를 설정할수 도 있음

마치 docker 의 bind mount 하는 것과 같음 (-v /data:/app/demo) 

위 예제에서는 호스트 머신의 volumes.hostPath.path 와 pods 내의 containers.volumeMounts.mountPath 가 연결됨

 

type: DirectoryOrCreate 은 path 로 설정한 dir 가 존재하지 않으면 새로 생성하라는 의미

type: Directory 로 설정할 수 있으나, 만약 path 로 설정한 dir 가 존재하지 않으면 실패함

 

hostPath driver 를 설정해두면, pod 가 죽어도 데이터는 유지됨

왜냐하면 데이터는 호스트 머신 위에 존재하기 때문

 

hostPath 를 통해 여러 pods 에서 하나의 동일 dir 를 바라보게 한 것까지는 좋았지만

다른 노드(즉, 다른 호스트 머신)에 존재하는 다양한 pods 는 하나의 dir 를 바라볼 수 없음

오직 하나의 노드(하나의 호스트 머신) 위에 존재하는 pods 만이 동일 dir 를 바라볼 수 있음

그래서 hostPath driver 는 여러 호스트 머신 위에 kube 가 실행되는 실제 운영 환경 등에서 적용 불가

 


 

 

[persistent volume documentation]

 

kube 는 데이터를 영구적으로 저장 가능한 Persistent Volumes 기능을 지원함

pods 및 Nodes 독립성에 대한 아이디어를 기반으로 구축되었기 때문에

Persistent Volume 은 pods 나 Nodes 로부터 완전히 독립되어있음

(pods 나 Nodes 의 life cycle 에 영향을 받지 않는다는 말)

엔지니어는 이 volume 이 구성되는 방식에 대한 완전한 권한을 갖게 됨

각 pods 와 각 deployment yaml 파일 등에 volume 설정을 여러번 할 필요가 없음

대신 한 번만 정의하고 여러 pods 에서 사용하도록 만듦

 

 

Persistent Volume(이하 PV) 는 Cluster 내에 존재하며,

Nodes 외부에 존재함 (따라서 Nodes 및 Pods 로부터 독립성을 갖게 됨)

 

Nodes 내부에 PV 와의 연결을 위한 PV Claim 이란 것을 생성함

PV Claim 은 (Pods 가 실행되는) Nodes 에 귀속됨(Nodes 에 의존성이 있음)

PV Claim 은 PV 에 도달 및 접근하여 데이터를 읽어올 수 있음

그래서 Pod 내의 Container 가 PV 에 접근하여 데이터를 읽을 때 중간에서 도움을 줌.

 

여러 PV 를 바라보는 PV Claim 을 가질 수 있으며

여러 PV Claim 이 하나의 PV 를 바라볼 수 있음

 

 

참고) [AWS Documentation]

CSI (Container Storage Interface)는 Kubernetes에서 다양한 스토리지 솔루션을 쉽게 사용할 수 있도록 설계된 추상화임
다양한 스토리지 공급업체는 CSI 표준을 구현하는 자체 드라이버를 개발하여
스토리지 솔루션이 Kubernetes와 함께 작동하도록 할 수 있음
(연결되는 스토리지 솔루션의 내부에 관계없이)
AWS는 Amazon EBS , Amazon EFS 및 Amazon FSx for Lustre 용 CSI 플러그인을 제공함

 

 

 


 

 

 

 

hostPath 를 사용하는 Persistent Volume 샘플을 만들어서 이해도를 높여봄

(Persistent Volume 에서 hostPath 를 사용한다는 말은,

Cluster 를 단일 노드 하나에서만 정의한다는 말이 됨)

 

host-pv.yaml 이라는 이름의 yaml 파일을 새로 만들어서 Persistent Volume 을 정의해 봄

 


< host-pv.yaml >

apiVersion: v1
kind: PersistentVolume
metadata:
  name: host-pv
spec: 
  capacity:
    storage: 4Gi
  volumeMode: Filesystem  #Filesystem 과 Block 으로 나뉨
  storageClassName: standard  #default sc 이름이 standard 라서 standard 를 넣음
  accessModes:
    - ReadWriteOnce
  hostPath: 
    path: /data  #노드의 /data 가 PV 에 연결됨
    type: DirectortOrCreate


 

 

accessModes 는 아래와 같은 mode 를 제공함

 - ReadWriteOnce : 단일 노드로부터의 rw 요청을 받을 수 있도록 mount 되는 Volume
 - ReadOnlyMany : 여러 노드로부터의 r 요청만 받을 수 있도록 mount 되는 Volume
 - ReadWriteMany : 여러 노드로부터의 rw 요청을 받을 수 있도록 mount 되는 Volume

(위 예제에서 hostPath 는 단일 노드 환경에서만 사용 가능하므로

사용할 수 있는 mode 는 오직 ReadWriteOnce 뿐임)

 

Kube 는 Storage Class 라는 개념을 갖고 있음

kubectl get sc 명령으로 확인 가능하며 default sc 도 존재함

 

Storage Class 는 kube 에서 관리자에게

Storage 관리 방법과 Volume 구성 방법을 세부적으로 제어할 수 있게 해주는 개념임

SC 는 hostPath Storage 를 프로비저닝 해야하는 정확한 Storage 를 정의함 (?뭔말임?) 

 

PV 는 한 번만 설정하면(심지어 다른 사람이 설정해도 됨)

여러 pods 에서 함께 접근하여 사용 가능한 저장소가 됨

이렇게 생성한 PV 를 Pods 가 접근해서 사용 가능하게 만들려면

pods 가 PV 에 접근하는 것을 도와주는 PV Claim 을 생성해야 함

 

두 가지를 추가 설정하면 됨

1. PV Claim 을 생성하는 yaml

2. (PV 를 사용하려는) pods 의 yaml 에서 PV Claim 사용하도록 설정

 

1번(PV Claim 을 위한 yaml)을 생성해보자.

이름은 host-pvc.yaml

 


< host-pvc.yaml >

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: host-pvc
spec:
  volumeName: host-pv  #위에 PV 를 정의할 때 설정한 이름을 여기 넣어서, 해당 PV 와 PVC를 연결함
  accessModes:
    - ReadWriteOnce
  storageClassName: standard  #default sc 이름이 standard 라서 standard 를 넣음
  resources:
    requests:
      storage: 4Gi  #PVC 에서 요청하는 크기는, PV 의 stoage 이하만 가능

 

2번(pods 의 yaml 에 PVC 사용 설정)을 추가 설정해보자

deployment yaml 에서 pods 의 spec 을 정의하는 부분에 volumes 를 아래와 같이 설정

 

....
    spec:
      containers:
        - name: demo
          image: demo
          volumeMounts:
            - mountPath: /app/demo
              name: demo-volume
      volumes:
        - name: demo-volume
          persistentVolumeClaim:
            claimName: host-pvc  #위에서 PVC 를 정의할 때 사용한 이름을 가져다가 설정함

 

 

PV 가 데이터를 저장하는 곳은 어디이기에 Nodes 와 Pods 로부터 독립적인걸까?

PVC 가 필요한 이유는 무엇일까?

왜 PV 와 Pods 사이에 중간다리 역할이 필요한거지?

PV 의 권한과 PVC 의 권한은 왜 구분되어있는걸까?

 


 

 

 

위에서 만든 PV yaml 와 PVC yaml 및 업데이트한 deployment yaml 을 kube 에 띄움

kubectl apply -f=host-pv.yaml

kubectl apply -f=host-pvc.yaml

kubectl apply -f=deployment.yaml

 

PV, PVC 가 잘 올라왔는지 아래 명령어로 확인

kubectl get pv 

kubectl get pvc

 


 

 

일반 Volume 와 Persistent Volume 의 차이를 알아봄

 

  일반 Volumes Persistent Volumes
Container 의존성 Container 재시작, 제거 되어도 데이터 유지 Container 재시작, 제거 되어도 데이터 유지
Pod 의존성 emptyDir 타입이라면, Pods 가 사라지면 데이터도 사라짐
hostPath 를 사용한다면 Pods 가 사라져도 데이터가 사라지진 않지만, Nodes 에 종속되기 때문에 (Nodes 가 내려가면 사용 불가, 단일 Node 에서만 사용 등에 이유로) 글로벌 수준에서 관리하기 어려움
Pod 의존성 없음
Node 의존성 없음
설정 위치 Pods 를 정의하는 deployment yaml 에 설정 PV 를 위한 yaml 에 설정
구성이 하나의 파일에 standalone 으로 존재하므로 재사용하거나 관리하기 용이함
프로젝트 크기 소규모 프로젝트에서 사용하기에 용이 대규모 프로젝트에서 사용하기에 용이

 

 


 

 

 

deployment yaml 에 설정된 값을 (Container 내에서 동작하는) application code 내에서 사용 가능

마치 환경 변수마냥 사용하는 것임

예를 들어 다음과 같은 app.js 코드가 존재하고


< app.js >

...
const filePath = path.join(__dirname, 'story', 'text.txt');
...

 

위 코드는, 아래와 같은 deployment yaml 로 실행될 Pods 내에서 실행될꺼라고 해보자


< deployment yaml>

....
    spec:
      containers:
        - name: story
          image: story-image
          volumeMounts:
            - mountPath: /app/story
              name: story-volume
....

 

 

deployment yaml 에 코드에서 사용 가능한 환경 변수를 추가해주고,


< deployment yaml>

....
    spec:
      containers:
        - name: story
          image: story-image
          env:
            - name: STORY_DIR
              value: 'story'
          volumeMounts:
            - mountPath: /app/story
              name: story-volume
....

 

app.js 코드 내의 'story' 를 설정한 환경변수명으로 바꿔줌


< app.js >

...
const filePath = path.join(__dirname, process.env.STORY_DIR, 'text.txt');
...

 

 

모든 언어에서 process.env.[name] 형식으로 환경변수를 가져오진 않을 것 같은데

이건 언어에서 환경 변수 가져오는 방법에 따라 달라질 듯

 

 


 

 

위와 같이 yaml 자체에 환경 변수값을 넣는 방법 외에,

환경 변수를 정의한 yaml 파일을 만들어서 환경변수값을 가져와 넣는 방법이 존재함

이해를 위해 env.yaml 을 만들어보자

 


< env.yaml >

apiVersion: v1
kind: ConfigMap
metadata:
  name: myenv
data:
  mydir: 'story'
  mykey1: 'myvalue1'
  mykey2: 'myvalue2'

 

kubectl apply -f=env.yaml

kubectl get configmap

 

그리고 deployment yaml 에 코드에서 env.yaml 에서 설정한 값을 가져옴


< deployment yaml>

....
    spec:
      containers:
        - name: story
          image: story-image
          env:
            - name: STORY_DIR
              valueFrom:
                configMapKeyRef:
                  name: myenv
                  key: mydir

          volumeMounts:
            - mountPath: /app/story
              name: story-volume
....

 

app.js 에서 process.env.STORY_DIR 로 읽은 값은 여전히 'story' 가 될 것임

 

더 복잡한데..?

 

 

 

 

 

+ Recent posts