본문 바로가기
AWS Cloud School 8기/쿠버네티스

[쿠버네티스] PV(Persistent Volume)/ AccessMode/ StorageClass/ ConfigMap/ Secret

by YUNZEE 2025. 4. 16.
728x90

PV (Persistent Volume)란?

데이터 저장소를 영구적으로 관리하는 리소스

pod가 죽거나 다시 시작돼도 데이터를 보존할 수 있게 해주는 디스크 공간을 제공함

- 영구적인 볼륨

- 물리적인 자원을 뜻함

- 도커에서 -v 같은 옵션을 통해 컨테이너의 데이터를 영구적으로 호스트에 백업을 했던 적이 있음.

- 왜냐하면 컨테이너가 삭제되면 내부의 데이터는 사라지기 때문임.

- 컨테이너의 데이터는 영구적이지 않음

PV와 PVC의 관계

쿠버네티스에서는 PV랑 PVC를 구분해서 사용함

PV: 실제 디스크 공간을 나타내는 리소스, 클러스터의 관리자가 영구적 저장소

PVC: 사용자가 요청하는 스토리지, pod가 필요한 저장소 크기와 사용 방법을 요청할 수 있음

더보기

- PV로 사용할 공간을 NFS로 미리 정의해놓자

- master노드에 NFS-server를 설치하고, worker노드들을 NFS-client로 구성

 

root@master:~# apt-get install -y nfs-kernel-server

- 마스터에 nfs-server 설치

 

root@master:~# mkdir /shared

root@master:~# chmod 777 -R /shared

root@master:~# vi /etc/exports

 

/shared *(rw,sync,no_subtree_check,no_root_squash)

- sync: 데이터가 정상적으로 저장된 후 응답

- no_subtress_check: 하위디렉터리 검사 x => 속도 증가

- no_root_squash: 외부 root 계정도 이 서버의 root계정처럼 동작함

 

root@master:~# systemctl restart nfs-server

root@master:~# systemctl enable nfs-server

root@master:~# showmount -e

- 워커노드인 worker 1이랑 worker2에 클라이언트 설치

root@worker-1:~# apt-get install -y nfs-common

 

root@worker-1:~# apt-get install -y nfs-common

root@worker-1:~# mkdir /shared

root@worker-1:~# mount -t nfs 211.183.3.100:/shared /shared

- 마운트포인트와 마운트대상을 동일하게 /shared로 하자.

 

root@worker-2:~# apt-get install -y nfs-common

root@worker-2:~# mkdir /shared

root@worker-2:~# mount -t nfs 211.183.3.100:/shared /shared

root@worker-2:~# touch /shared/test

- 마운트가 잘 된 걸 확인.

 

 

root@worker-1:~# vi /etc/fstab

211.183.3.20:/shared /shared nfs defaults 0 0

root@worker-2:~# vi /etc/fstab

211.183.3.20:/shared /shared nfs defaults 0 0

 

pv 생성

root@master:~# cd ~/mani/

root@master:~/mani# mkdir pv

root@master:~/mani# cd pv/

 

vi pv1.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs-pv1
spec:
  capacity:
    storage: 1Gi #용량
  accessModes: 
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /shared/pv1
    server: 211.183.3.20

- 용량: 나중에 pvc를 통해 pv를 요청할 때, 요청량 > pv의 용량이면 pvc와 pv가 연동이 안됨

-> 만약에 100Gi를 요청(pvc)했는데 pv가 1Gi라면 성립이 안됨

 

vi pvc1.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

- 요청하는 용량(1Gi)은 pv보다 작거나 같아야 됨

- accessModes가 호환이 되어야 됨

kubectl apply -f pv1.yml

# pv 생성

 

kubectl apply -f pvc1.yml

# pvc 생성

- pv와 pvc의 용량, access mode 같은 것들이 호환이 됐기 때문에 서로 bound가 됨

- 이번엔 이 pv를 사용할 pod를 한번 생성해 보자

- status가 Available에서 Bound로 바뀜 -> pvc요청을 받을 수 있는 상태에서 pvc파일을 설정하고 나서 요청을 받은 상태가 됨

- volume이 nfs-pv1이 됨

 

vi pod1.yml

이번에는 pv를 사용할 pod를 한 번 생성해 보자

apiVersion: v1
kind: Pod
metadata:
  name: nfs-pod1
spec:
  containers:
  - name: nfs-con
    image: 61.254.18.30:5000/ipnginx
    volumeMounts:
    - name: nfs-vol
      mountPath: /vol
  volumes:
  - name: nfs-vol
    persistentVolumeClaim:
      claimName: nfs-pvc

kubectl apply -f pod1.yml

kubectl exec -it nfs-pod1 -- bash

root@nfs-pod1:/# echo test > /vol/test.txt

- pod 내에서 파일 생성

- Ctrl + D로 컨테이너 밖으로 빠져나온 다음

- 호스트에 존재하는 test.txt 파일을 확인.

- 컨테이너의 데이터가 호스트에 잘 백업된 걸 확인 가능함.

- 노드에서 컨테이너 내부 파일 확인.

 

root@master:~/mani/pv# kubectl delete pod nfs-pod1

# 파드 삭제

 - pv와 pvc는 pod가 삭제되어도 잘 살아있음

-> pod는 결국 여러 차례 삭제 및 재성성되어도 동일한 데이터가 유지될 것

 

kubectl apply -f pod1.yml

- pod 생성

 

kubectl exec nfs-pod1 -- ls /vol

- 파드가 삭제 및 재성생 될 때, 동일한 pvc에만 volumeMount 된다면, 데이터는 동일하게 유지됨

PV를 프로비저닝 하는 두 가지 방식

- 수동(static)

-> pvc요청이 올 때 생성을 해주거나, 올 것을 예상해서 pv를 미리 생성해두는 방식

-> 위에서 실습했던 방식

 

- 자동(dynamic)

-> pvc요청이 올때 자동으로 pv를 생성

-> 다이나믹 프로비저너를 설치

 

- Reclaim 방식(PVC가 삭제 됐을 때 정책)

- Recycle 방식: 재활용 PVC가 삭제된 다음에 다른 PVC요청이 오면 사용 가능.

현재는 정책상 사용 불가함

AccessMode

pv-pvc의 모드가 같아야 bound 됨. 일치하지 않으면. pending 상태에 머무름.

 

ReadWriteOnce(RWO) : 단일 노드에서 읽기 및 쓰기 가능.

ReadOnlyMany(ROX) : 다수 노드에서 읽기만 가능.

ReadWriteMany(RWX) : 여러 노드에서 읽기 및 쓰기 가능.

ReadWriteOncePod(RWOP) : 단일 Pod에서만 읽기 및 쓰기 가능.

실습 1)

=> 모드를 맞춰줘야 됨

https://kubernetes.io/ko/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/

wordpress.yml 파일

 

mysql.yml 파일

 

위 링크에 존재하는 wordpress와 mysql 매니페스트를 위와 같이 수정하고,

이 매니페스트에서 필요한 리소스를 생성하며 wordpress가 잘 동작하고 접속 가능하도록 만들어보자

더보기

풀이

- wordpress pod에서 mysql을 어떻게 찾아갈 수 있냐? 쿠버네티스에서는 서비스의 이름이 곧 주소.

- mysql svc의 이름은 wordpress-mysql 이므로 wordpress-mysql 이것 자체가 주소가 된다.

 

1. pv 생성

vi pv.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
spec:
  capacity:
    storage: 2Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /shared/pv2
    server: 211.183.3.20
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv3
spec:
  capacity:
    storage: 2Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /shared/pv3
    server: 211.183.3.20

kubectl apply -f pv.yml

 

2. mysql 매니페스트

 

vi mysql.yml

- 추가 수정한 내용이 있으니 참고하시오!

apiVersion: v1
kind: Service
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  ports:
    - port: 3306
  selector:
    app: wordpress
    tier: mysql
  clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress-mysql
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: mysql
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: mysql
    spec:
      containers:
      - image: 61.254.18.30:5000/mysql
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pv-claim

3. wordpress.yml 에서 수정할 부분

vi wordpress.yml

apiVersion: v1
kind: Service
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  ports:
    - port: 80
  selector:
    app: wordpress
    tier: frontend
  type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: wordpress
  labels:
    app: wordpress
spec:
  selector:
    matchLabels:
      app: wordpress
      tier: frontend
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: wordpress
        tier: frontend
    spec:
      containers:
      - image: 61.254.18.30:5000/wp
        name: wordpress
        env:
        - name: WORDPRESS_DB_HOST
          value: wordpress-mysql
        - name: WORDPRESS_DB_PASSWORD
          value: password
          
        ports:
        - containerPort: 80
          name: wordpress
        volumeMounts:
        - name: wordpress-persistent-storage
          mountPath: /var/www/html
      volumes:
      - name: wordpress-persistent-storage
        persistentVolumeClaim:
          claimName: wp-pv-claim

- mysql을 어떻게 찾아갈 수 있냐? mysql의 서비스이름으로 찾아갈 수 있음

- 서비스 이름은 내가 정할 수 있음 = 항상 일정한 주소로 찾아갈 수 있음

StorageClass

- 스토리지에는 다양한 종류가 있을 수 있음

- 대표적인 예가 HDD, SDD를 비교하면 상대적으로  HDD는 느리고 SSD는 빠를 것임. 스토리지의 성격에 맞게 분류를 해놓는 게 스토리지클래스이며, 관리자입장에서는 용도에 맞게 스토리지를 분류해 놓고, 사용자 입장에서는 용도에 맞게 스토리지클래스를 지정해서 pvc로 요청하면 될 것 임

 

- 사용자 "빠른 스토리지로 부탁해요"

- 관리자 "그렇다면 상대적으로 빠른 SSD를 제공해 드릴게요~"

 

aws eks에 보면, gp2, gp3의 경우 SSD이고 efs-sc-EFS 스토리지 이런 다양한 스토리지들이 존재함

더보기

mkdir /shared/pv-sc

- 경로를 만들어주고

 

vi pv-pvc.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: manual-pv
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  storageClassName: manual-sc
  persistentVolumeReclaimPolicy: Retain
  nfs:
    path: /shared/pv-sc
    server: 211.183.3.100
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: manual-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: manual-sc

 

- pvc를 생성할 때 sc를 명시해서 생성하면, pv도 동일한 sc를 갖는 pv가 맵핑이 됨

ConfigMap

- 클러스터 존재하는 값이나 설정을 참조하여 사용하고 싶을 때 쓰는 리소스

- 쿠버네티스에서 애플리케이션의 설정 데이터를 관리하는 방법 중 하나임

-> 애플리케이션 설정 파일이나 환경 변수 등을 쿠버네티스 클러스터 내에서 관리하고, 이를 pod와 같은 리소스에 전달하는 역할을 함

ex) 환경변수 - username, db_host 등...

ex) nginx.conf 이나 리버스프록시 설정 파일 default.conf같은 설정 파일의 내용을 Pod(컨테이너)에 넣어줄 수 있음

더보기

root@master:~/mani/cm# pwd

/root/mani/cm

 

kubectl create cm info --from-literal username=root --from-literal db_host=211.183.3.233

- username이라는 키(key)에 해당하는 값(value) root

 

kubectl delete cm info

 

vi info.yml

apiVersion: v1
kind: ConfigMap
metadata:
  name: info
  namespace: default
data:
  db_host: 211.183.3.30
  username: root

 

kubectl apply -f info.yml

- 여기서 ip가 뭐가 나오든 상관없음

apiVersion: v1
kind: Pod
metadata:
  name: my-pod-env
spec:
  containers:
  - name: my-container
    image: 61.254.18.30:5000/nginx
    env:
    - name: USERNAME
      valueFrom:
        configMapKeyRef:
          name: info
          key: username
    - name: DATABASE
      valueFrom:
        configMapKeyRef:
          name: info
          key: db_host

- pod안에서 USERNAME이라는 환경변수를 조회해 보면,  root라는 값이 들어가 있을 것 임

root@master:~/mani/cm# kubectl exec my-pod-env -c my-container -- env | grep USERNAME
USERNAME=root

root@master:~/mani/cm# kubectl exec my-pod-env -- env | grep DATABASE
DATABASE=211.183.3.30

 

이번에는 설정파일을 cm으로 만들어보겠음

vi conf.yml

apiVersion: v1
kind: ConfigMap
metadata:
  name: conf
data:
  nginx.conf: |
    asdf;lksjdflsakjfsa;d
    sad;flksdfl;jsadfl;jsadfl;k
    asdf;lkjsadf;lksjdfl;ksdja
    sad;flkjsadfl;ksadjf;l
    sadf;lksjdf;

- 파이프라인(|): 하위에 여러 줄의 명령어를 입력할 수 있음

- nginx.conf 파일에 아래의 내용이 포함이 될 것

 

vi conf-pod.yml

apiVersion: v1
kind: Pod
metadata:
  name: my-pod-conf
spec:
  containers:
  - name: my-container
    image: 61.254.18.30:5000/nginx
    volumeMounts:
    - name: nginx-config-vol
      mountPath: /nginx.conf
      subPath: nginx.conf
  volumes:
  - name: nginx-config-vol
    configMap:
      name: conf
      items:
      - key: nginx.conf
        path: nginx.conf

subPath: ConfigMap(conf)에서 특정한 키, 파일만 마운트 할 때. 마운트포인트의 기존내용 유지하는 옵션.

path: 컨테이너 외부에 존재하는 파일이라는 뜻. 내부에 MountPath에 넣어주겠음

 

kubectl apply -f conf-pod.yml

kubectl exec my-pod-conf -- cat /nginx.conf

 - 우리가 구성한 설정 파일의 내용이 잘 들어 있는 걸 확인 가능

Secret

- ConfigMap과 비슷하게, 특정한 값이나 설정을 컨테이너 안으로 주입을 하는 개념. 다른 점이 있다면 보안 수준이 다름

- 민감한 정보(데이터베이스의 암호 같은..)를 외부에 호출해서 쓰는 방식. base64 방식으로 인코딩 되며, etcd에서 암호화.

- 시크릿을 제외한 다양한 리소스들은 결국 api요청만 한다면 정보를 조회할 수 있는 반면, 시크릿의 경우엔 etcd에서 암호화가 되기 때문에 조회가 불가능.

더보기

echo test123 | base64

- test123이라는 평문을 base64 방식으로 인코딩.

echo dGVzdDEyMwo= | base64 --decode

- 인코딩 된 값을 디코딩

kubectl create secret generic sec --from-literal password=test123

- 시크릿을 명령어로 생성

kubectl get secret

- 시크릿 조회

kubectl delete secret sec

- 삭제

vi sec.yml

- 매니페스트로 정의

apiVersion: v1
kind: Secret
metadata:
  name: sec
type: Opaque # 기본 타입
stringData:
  password: test123 #password 값

type: Opaque => <key>:<value> 형태를 뜻함.

 

vi sec-pod.yml

- 이 시크릿을 참조하는 pod 생성

apiVersion: v1
kind: Pod
metadata:
  name: pod-sec
spec:
  containers:
  - name: sec-con
    image: 61.254.18.30:5000/nginx
    envFrom:
    - secretRef:
        name: sec

kubectl apply -f sec-pod.yml

kubectl exec pod-sec -- env | grep password

오류/ 해결 방안 - http와 https의 충돌?

더보기

 Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  2m1s              default-scheduler  Successfully assigned default/pod-sec to worker-2
  Normal   Pulling    2m                kubelet            Pulling image "61.254.18.30:5000/nginx"
  Normal   Pulled     2m                kubelet            Successfully pulled image "61.254.18.30:5000/nginx" in 30ms (30ms including waiting). Image size: 73096092 bytes.
  Normal   Created    2m                kubelet            Created container: sec-con
  Normal   Started    2m                kubelet            Started container sec-con
  Normal   Pulling    24s (x4 over 2m)  kubelet            Pulling image "61.254.18.30:5000/nginx"
  Warning  Failed     24s (x4 over 2m)  kubelet            Failed to pull image "61.254.18.30:5000/nginx": failed to pull and unpack image "61.254.18.30:5000/nginx:latest": failed to resolve reference "61.254.18.30:5000/nginx:latest": failed to do request: Head "https://61.254.18.30:5000/v2/nginx/manifests/latest": http: server gave HTTP response to HTTPS client
  Warning  Failed     24s (x4 over 2m)  kubelet            Error: ErrImagePull
  Normal   BackOff    10s (x6 over 2m)  kubelet            Back-off pulling image "61.254.18.30:5000/nginx"
  Warning  Failed     10s (x6 over 2m)  kubelet            Error: ImagePullBackOff

HTTP와 HTTPS 간의 연결 문제로 인해 이미지 다운로드가 실패하는 상황임

그래서 

apiVersion: v1
kind: Pod
metadata:
  name: pod-sec
spec:
  containers:
  - name: sec-con
    image: 61.254.18.30:5000/nginx #image: public.ecr.aws/nginx/nginx:latest
    envFrom:
    - secretRef:
        name: sec

 

 여기를 바꿔주니까

Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  29s   default-scheduler  Successfully assigned default/pod-sec to worker-2
  Normal  Pulling    29s   kubelet            Pulling image "public.ecr.aws/nginx/nginx:latest"
  Normal  Pulling    28s   kubelet            Pulling image "public.ecr.aws/nginx/nginx:latest"
  Normal  Pulled     19s   kubelet            Successfully pulled image "public.ecr.aws/nginx/nginx:latest" in 9.98s (9.98s including waiting). Image size: 73110238 bytes.
  Normal  Created    18s   kubelet            Created container: sec-con
  Normal  Started    18s   kubelet            Started container sec-con
  Normal  Pulled     16s   kubelet            Successfully pulled image "public.ecr.aws/nginx/nginx:latest" in 12.079s (12.079s including waiting). Image size: 73110238 bytes.
  Normal  Created    16s   kubelet            Created container: sec-con
  Normal  Started    16s   kubelet            Started container sec-con

 

event부분에 에러가 안 생기고 

root@master:~/mani/cm# kubectl exec pod-sec -- env | grep password
password=test123

결괏값이 잘 나오는 걸 확인할 수 있음

 

- 정확하게 어디서 충돌 나는 건지는 모르겠음 

 

kubectl exec pod-sec -- env | grep password

 

728x90