74일차) 2025-04-15(쿠버네티스 - pv, StorageClass, ConfigMap, Secret, wp/mysql/3tier)
PV(Persistent Volume)
영구적인 볼륨, 물리적인 자원
도커에서 -v 같은 옵션을 통해 컨테이너의 데이터를 영구적으로 호스트에 백업함, 왜냐하면 컨테이너가 삭제되면 내부의 데이터는 사라지기 때문, 컨테이너의 데이터는 영구적이지 않음
PV로 사용할 공간을 NFS로 미리 정의 해놓자 !
master 노드에 NFS-server를 설치하고, worker 노드들을 NFS-client로 구성
- 마스터에 nfs-server 설치
root@master:~# apt-get install -y nfs-kernel-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)
root@master:~# systemctl restart nfs-server
root@master:~# systemctl enable nfs-server
root@master:~# showmount -e
- 워커노드에 클라이언트 설치
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
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
- 워커노드에 test 확인
root@worker-2:~# touch /shared/test
- worker1, 2에서 마운트 유지
root@worker-1:~# vi /etc/fstab
211.183.3.100:/shared /shared nfs defaults 0 0
root@worker-2:~# vi /etc/fstab
211.183.3.100:/shared /shared nfs defaults 0 0
pv 생성
root@master:~# cd ~/mani/
root@master:~/mani# mkdir pv
root@master:~/mani# cd pv/
vi pv1.yml
# 용량 : 나중에 pvc를 통해 pv를 요청할때, 요청량 > pv의 용량이면 pvc와 pv가 연동이 안된다.
# ex. 100Gi 를 요청(pvc)했는데 pv가 1Gi라면 성립이 안될 것.
apiVersion: v1 kind: PersistentVolume metadata: name: nfs-pv1 spec: capacity: storage: 1Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /shared/pv1 server: 211.183.3.100 |
- pv를 생성했다면 이번엔 pvc를 통해 스토리지를 요청
vi pvc1.yml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nfs-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi |
# 요청하는 용량(1Gi)은 pv보다 작거나 같아야 함
# accessModes가 호환이 되어야 함
- pv 생성
root@master:~/mani/pv# kubectl apply -f pv1.yml
root@master:~/mani/pv# kubectl get pv
root@master:~/mani/pv# kubectl apply -f pvc1.yml
# 요청에 의해 pvc가 pv에 바운드 됐음
# pv와 pvc의 용량, access mode 같은것들이 호환이 됐기때문에 서로 bound 됐음
이번엔 이 pv를 사용할 pod를 한번 생성해보자 !
root@master:~/mani/pv# mkdir /shared/pv1
root@master:~/mani/pv# vi pod1.yml
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
- pod 안에 nfs-con 안으로 접속
kubectl exec -it nfs-pod1 -- bash
- pod내에서 파일 생성
root@nfs-pod1:/# echo test > /vol/test.txt
- Ctrl + D 로 컨테이너밖으로 빠져나온다음, 호스트에 존재하는 test.txt 확인
- 파드 삭제
root@master:~/mani/pv# kubectl delete pod nfs-pod1
→ pod는 결국 여러차례 삭제 및 재생성되어도 동일한 데이터가 유지가 될 것
- pod 다시 생성
kubectl apply -f pod1.yml
kubectl exec nfs-pod1 -- ls /vol
# 파드가 삭제 및 재성생 될때, 동일한 pvc에만 volumeMount된다면, 데이터는 동일하게 유지된다.
1. 📦 Provisioning 방식 (PV를 준비, 제공하는 방식)
- 수동(static) provisioning
pvc요청이 올때 생성을 해주거나, 올것을 예상해서 pv를 미리 생성해두는 방식, 위에서 실습했던 방식
ex. mkdir /shared/pv1 으로 경로를 미리 만들어주고, pv를 생성 - 자동(dynamic) provisioning
pvc요청이 올때 자동으로 pv를 생성
→ 다이나믹 프로비저너를 설치
2. 🔁 Reclaim 정책 (PVC 삭제 시 PV 처리 방식)
- Retain
PVC가 삭제되면 pv는 유지, 재활용은 불가능, 새로운 PVC에 바인드가 되지 않음, 실제 디스크도 영구적으로 유지 - Delete
pvc가 삭제되면 pv도 삭제 - Recycle
재활용, PVC가 삭제된 다음에 다른 PVC요청이 오면 사용 가능, 현재는 정책상 사용 불가
3. 🔐 Access Mode (접근 모드)
pv-pvc의 모드가 같아야 bound됨. 일치하지 않으면. pending 상태에 머무름.
- ReadWriteOnce(RWO) : 단일 노드에서 읽기 및 쓰기 가능
- ReadOnlyMany(ROX) : 다수 노드에서 읽기만 가능
- ReadWriteMany(RWX) : 여러 노드에서 읽기 및 쓰기 가능
- ReadWriteOncePod(RWOP) : 단일 Pod에서만 읽기 및 쓰기 가능
→ 모드를 맞춰줘야 한다.
실습)
https://kubernetes.io/ko/docs/tutorials/stateful-application/mysql-wordpress-persistent-volume/
- 커스터마이즈 파일 구성x
- wordpress.yml
- mysql.yml
위 링크에 존재하는 wordpress와 mysql 매니페스트를 위와 같이 수정하고, 이 매니페스트에서 필요한 리소스를 생성하며 wordpress가 잘 동작하고 접속가능하도록 만들어보세요.
- wordpress.yml
서비스 타입 조정 (LoadBalancer → NodePort 권장, 로컬 환경 기준)
로컬 k8s 클러스터에서는 일반적으로 LoadBalancer 타입을 사용할 수 없는 대신, NodePort로 수정해 노드 IP와 포트로 접근한다:
root@master:~/mani/pv# vi wordpress.yml
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
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: wordpress:4.8-apache
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.yml
root@master:~/mani/pv# cat 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:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
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: mysql:5.6
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
- PV 생성
root@master:~/mani/pv# cat mypv.yml
# mysql-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data/mysql" # 워커 노드의 실제 경로
---
# wp-pv.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: wp-pv
spec:
capacity:
storage: 20Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data/wordpress"
- 기존 상위 리소스까지 전부 삭제
kubectl delete deploy --all
kubectl delete rs --all
kubectl delete pod --all
- yml 실행
# 1. PV 생성
kubectl apply -f mypv.yml
# 2. MySQL 배포
kubectl apply -f mysql.yml
# 3. WordPress 배포
kubectl apply -f wordpress.yml
풀이)
# wordpress pod에서 mysql을 어떻게 찾아갈 수 있는가 → 쿠버네티스에서는 서비스의 이름이 곧 주소
# mysql svc의 이름은 wordpress-mysql 이므로 wordpress-mysql 이것 자체가 주소가 된다.
# 라벨링 여러개 가능
- 1. pv 생성
pvc가 두개 있기 때문에 pv도 두개를 생성, 현재 pvc는 pv를 고를 수 없는 상태
apiVersion: v1 kind: PersistentVolume metadata: name: pv2 spec: capacity: storage: 2Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /shared/pv2 server: 211.183.3.100 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv3 spec: capacity: storage: 2Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain nfs: path: /shared/pv3 server: 211.183.3.100 |
kubectl apply -f pv.yml
- 2. mysql 매니페스트
vi mysql.yml
# svc에서 중요한 부분
# pvc에서 수정할 부분
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. wp.yml 에서 수정할 부분
# mysql을 어떻게 찾아갈 수 있냐? mysql의 서비스이름으로 찾아갈 수 있다.
# 서비스이름은 내가 정할 수 있다 = 항상 일정한 주소로 찾아갈 수 있다.
vi wp.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 |
StorageClass
스토리지에는 다양한 종류가 있을 수 있다. 스토리지의 성격에 맞게 분류를 해놓은게 스토리지클래스이며, 관리자입장에서는 용도맞게 스토리지를 분류해놓고, 사용자입장에서는 용도에 맞게 스토리지클래스를 지정해서 pvc로 요청하면 될 것이다.
ex. HDD, SSD를 비교하면 상대적으로 HDD는 느리고, SSD를 빠르다.
사용자 : 빠른 스토리지 요청
관리자 : 상대적으로 빠른 SSD 제공
aws eks에 보면,
gp2, gp3의 경우 SSD
efs-sc - EFS 스토리지
root@master:~/mani/pv# mkdir sc
root@master:~/mani/pv# cd sc/
root@master:~/mani/pv/sc# mkdir /shared/pv-sc
root@master:~/mani/pv# 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가 맵핑이 된다.
root@master:~/mani/pv/sc# kubectl apply -f pv-pvc.yml
ConfigMap
클러스터 존재하는 값(value)이나 설정을 참조하여 사용하고 싶을때 쓰는 리소스
ex. 환경변수 - username, db_host 등
ex. nginx.conf나 리버스프록시 설정파일 default.conf 같은 설정파일의 내용을 pod(container)에 넣어줄 수 있다.
root@master:~/mani# mkdir cm
root@master:~/mani# cd cm/
root@master:~/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
root@master:~/mani/cm# vi info.yml
apiVersion: v1 kind: ConfigMap metadata: name: info namespace: default data: db_host: 211.183.3.30 username: root |
root@master:~/mani/cm# kubectl apply -f info.yml
root@master:~/mani/cm# kubectl describe cm info
root@master:~/mani/cm# vi pod-env.yml
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 |
root@master:~/mani/cm# kubectl apply -f pod-env.yml
# pod안에서 USERNAME이라는 환경변수를 조회해보면, root라는 값이 들어가 있을 것이다.
root@master:~/mani/cm# kubectl exec my-pod-env -- env | grep USERNAME
root@master:~/mani/cm# kubectl exec my-pod-env -- env | grep DATABASE
이번에는 설정파일을 아무렇게나 한번 cm으로 만들어보자.
- conf.yml
root@master:~/mani/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 파일에 아래의 내용이 포함이 될 것.
root@master:~/mani/cm# kubectl apply -f conf.yml
- con-podf.yml
root@master:~/mani/cm# vi conf-pod.yml
apiVersion: v1 kind: Pod metadata: name: my-pod-conf spec: containers: - name: my-container image: 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에 넣어주겠다.
root@master:~/mani/cm# kubectl apply -f conf-pod.yml
root@master:~/mani/cm# kubectl exec my-pod-conf -- cat /nginx.conf
Secret
ConfigMap과 비슷하게 특정한 값이나 설정을 컨테이너 안으로 주입하는 개념
다른점: 보안수준, 민감한 정보(DB 암호 등)를 외부에 호출해서 쓰는 방식, base64 방식으로 인코딩되며, etcd에서 암호화
시크릿을 제외한 다양한 리소스들은 결국 api 요청만 한다면 정보를 조회할 수 있는 반면,
시크릿의 경우엔 etcd에서 암호화가 되기 때문에 조회가 불가능
- test123이라는 평문을 base64방식으로 인코딩
echo 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 |
# type: Opaque => <key>:<value> 형태를 뜻함
kubectl apply -f sec-pod.yml
- 이 시크릿(sec)을 참조하는 pod 생성
vi sec-pod.yml
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
자율과제)
db는 61.254.18.30:5000/mysql
was는 61.254.18.30:5000/tomcat:latest
web은 61.254.18.30:5000/nginx:latest
를 사용하여
3tier.com으로 접속했을때 dbconnection 성공이 뜨는 구조를 만들어보세요.
단, db의 정보는 미리 구성된 secret을 불러와야함.
web은 미리 구성된 nginx.conf라는 이름의 configMap을 사용하여야 한다.
도메인은 ingress로 구성.
was와 db는 headless svc로 구성한다.
라이브러리파일은 볼륨으로 구성하세요.
가이드라인
1.일단 어떤 리소스를 생성해서 연결시킬지 구조를 도식화 해보세요
2.각 티어별로 어떤 구성파일을 넣어줘야 할지 생각한다.
톰캣에서 db연동을 위한 jsp 파일의 정보는 configmap이나 secret말고 파일에 그냥 적으세요.
풀이)
[Client] ⇄ [Ingress (3tier.com)] ⇄ [nginx (web)] ⇄ [tomcat (was)] ⇄ [mysql (db)]
git clone https://github.com/oolralra/k8s-3tier.git
# 매니페스트 및 JDBD를 포함한 레포지토리.
mkdir -p /shared/tom
cp mysql-connector-java-8.0.23.jar /shared/tom
모든 노드 서스펜드 후 스냅샷 찍고,
kubeadm reset --cri-socket unix:///run/containerd/containerd.sock
# 클러스터 초기화. 모든 노드에서
# ip add 이런 찌꺼기가 남아있을 수 있으므로 재부팅.
=> 클러스터 재구성. 필요한 애드온 설치. 위 깃허브에 있는 3tier를 구성해보세요.
kubectl describe : pod를 완전자세히
kubectl logs : pod내의 앱 로그 확인 가능
1. 항상 클러스터가 정상인지 먼저 확인을 하는게 좋음
replicas 2개인 deploy 띄워서 모든 노드에 존재하는 pod가 접속(curl)되는지 확인
2. 안되면 kubectl get pod -n kube-system 해서 모든 파드들이 정상인지 확인
- db
vi db-sec.yml
db-dep.yml 파일을 apply 한다음에 환경변수가 제대로 구성되어있는지 확인
- tom
tom-configmap.yml
- jdbc를 /shared/tom에 저장
root@master:/k8s/3tier# wget -P /shared/tom https://repo1.maven.org/maven2/mysql/mysql-connector-java/8.0.23/mysql-connector-java-8.0.23.jar
pvpvc.yml를 apply해서 pv 와 pvc 생성
tom-dep.yml
- nginx
nginx 파드를 띄워서 내부에서 있는 default.conf 파일을 미리 수정을 해서 다시 넣어주는게 좋다.
vi nginx-configmap.yml
root@master:~/mani/k8s-3tier# vi deploy.yaml
♨ ingress-controller의 외부 IP가 none
→ NodePort에서 LoadBalancer로 수정
root@master:~/mani/k8s-3tier# kubectl apply -f deploy.yaml
♨ ingress-controller의 외부 IP가 pending
→ config-metal.yml 파일 구성 오류
root@master:~/mani/k8s-3tier# vi config-metal.yml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 211.183.3.200-211.183.3.240 # 원하는 대역대, 안겹치게 수정
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
- ingress-nginx 주소 확인
root@master:~/mani/ingress# vi /etc/hosts
211.183.3.200 3tier.com
# kubectl get svc -n ingress-nginx에서 controller의 주소
- 적용
kubectl apply -f db-sec.yml # 미리 구성한 Secret
kubectl apply -f pvpvc.yml # PV, PVC (NFS)
kubectl apply -f db.yml # DB: mysql + svc
kubectl apply -f tom-configmap.yml # WAS 설정 파일(index.jsp)
kubectl apply -f tom.yml # WAS: tomcat + svc
kubectl apply -f nginx-configmap.yml # Web 설정 파일(default.conf)
kubectl apply -f nginx.yml # Web: nginx + svc
kubectl apply -f ingress.yml # Ingress 설정
'AWS Cloud School 8기 > 쿠버네티스' 카테고리의 다른 글
76일차) 2025-04-17(컨테이너의 헬스체크-readinessProbe, 리소스-StatefulSet, DaemonSet) (0) | 2025.04.17 |
---|---|
75일차) 2025-04-16(RBAC, 3A, Dynamic Provisioner) (0) | 2025.04.16 |
73일차) 2025-04-14(쿠버네티스 리소스 - service/metallb, ingress) (0) | 2025.04.14 |
72일차) 2025-04-11(쿠버네티스 리소스-pod, replicaset, deploymnet, namespace, service) (0) | 2025.04.11 |
71일차) 2025-04-10(쿠버네티스-k8s-tem, k8s 컴포넌트, 매니페스트) (0) | 2025.04.10 |