📒 볼륨 개념 참조
1 . 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 # 볼륨과 디렉터리 링크
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: {}
-> emptydir은 파드안에 컨테이너들이 같은 볼륨을 바라보게 할 때 사용한다.
📗 nginx:alpine은 기본 이미지와 동일하게 작동하는데 용량은 훨씬 작다.
1 -1. 서비스 구성
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-fortune
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: myapp-rs-fortune
-> 외부에 노출시키기 위해 구성
kubectl logs myapp-rs-fortune-llkvx -c html-generator
kubectl logs myapp-rs-fortune-llkvx -c web-server
# 한 파드내의 각 컨테이너의 로그 기록
kubectl exec myapp-rs-fortune-llkvx -c web-server -- hostname
# 원하는 컨테이너에 exec 명령
kubectl exec myapp-rs-fortune-llkvx -it -c html-generator -- sh
# 원하는 컨테이너에 셸로 접속
-> 멀티 컨테이너인 경우 -c 옵션을 사용하여 파드안에서 컨테이너를 구분해서 정보를 확인하거나 접속한다.
-> curl 외부용ip를 통해 파드에 80번포트가 뚫려있는 webserver컨테이너에 도달한다. webserver컨테이너의 index.html내용을 curl의 결과로 보여줄텐데, index.html을 갖고있는 해당 디렉터리는 볼륨을 통해 보조기능을 하는 컨테이너의 htdocs디렉터리에 연결되어있다. 따라서 보조기능을 하는 컨테이너의 htdocs디렉터리의 index.html의 내용이 볼륨에 저장되고, 해당 index.html은 주기능을 하는 컨테이너의 볼륨이 연결된 디렉터리에 전달된다.
2 . gitRepo를 대안으로 사용하는 emptydir 사용 예시
<초기화 컨테이너>
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod-git
spec:
initContainers:
- name: git-clone3
image: alpine/git
args:
- clone
- --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: {}
hostpath볼륨을 설정할 떄 사용하는 type의 필드 값 종류
📒 파일도 마운트할 수 있다.
1 . 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
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 # 볼륨의 경로 지정
1-1 . 서비스 구성
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-hp
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: myapp-rs-hp
-> 노드의 특정 경로에 type= 디렉터리를 볼륨으로 지정하고 해당 볼륨을 마운트한다.
-> 만일 파드를 두개 만들었는데 node1,node2에 배치된 상태였다면, node1,node2에 /srv/web_contents 디렉터리가 존재해야 한다. 존재하지 않다면 파드는 만들어지지 않는다.
kubectl describe pod 파드명
-> 디렉터리가 존재하지 않아 파드 생성이 실패했음을 확인
kubectl logs 파드명
-> 애초에 파드가 만들어지지 않았기 때문에 로그확인을 하지 못한다
스토리지 관리는 컴퓨트 인스턴스 관리와는 별개의 문제다. 퍼시스턴트볼륨 서브시스템은 사용자 및 관리자에게 스토리지 사용 방법에서부터 스토리지가 제공되는 방법에 대한 세부 사항을 추상화하는 API를 제공한다. 이를 위해 퍼시스턴트볼륨 및 퍼시스턴트볼륨클레임이라는 두 가지 새로운 API 리소스를 소개한다.
기존의 볼륨은 파드에 볼륨을 생성하여 사용한다. 즉, 파드내에서 볼륨을 어떻게 사용할 것인가를 정의한 것이다. 다시말해, 파드를 삭제하면 결국에는 볼륨도 삭제된다는 것이다
= 파드의 생명주기에 볼륨이 분리되지 못하고 포함된다.
따라서 관리자영역에 스토리지를 구성하고, 개발자영역에 pod를 배치한다. 스토리지 앞에는 pv가 존재하고 pod앞에는 pvc가 존재한다. pod는 pvc를 통해 pv를 요청한다.
= 파드의 리소스와 볼륨의 리소스의 라이프 사이클을 분리시킨다.
📌 pv가 곧 스토리지랑 같다고 생각하면 된다
쿠버네티스는 볼륨으로 persistent Volume 사용을 권장한다.
💡 pv와 pvc를 연결하는 방법은 label or 이름이 있다.
- retain : pvc를 삭제한 후 pv가 남아있을 때 재사용을 할 수 없기 때문에 pv와 관련된 리소스를 반환(삭제)한다.
= 정적 프로비전에서 default정책- delete : pv를 포함한 스토리지 관련 모든 리소스 삭제
= 권장 하는 방식 , 동적 프로비전에서 dafault 정책- recycle : 더이상 사용하지 않는 정책이다
📘 접근모드 참조
1 . nfs 서버 구성
sudo apt install nfs-kernel-server -y
# 컨트롤 플레인에 nfs서버 설치
sudo mkdir /srv/nfs-volume
# 디렉터리 생성
echo "/srv/nfs-volume *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee /etc/exports
# export할 디렉터리 생성
cat /etc/exports
# export할 디렉터리 확인
sudo exportfs -arv
# 디렉터리 export
ansible kube_node -i ~/kubespray/inventory/mycluster/inventory.ini -m apt -a 'name=nfs-common' --become
# 각 노드에 ansible ad-hoc명령으로 nfs시스템 설치
PV를 프로비저닝 할 수 있는 두 가지 방법이 있다 : 정적(static) 프로비저닝과 동적(dynamic) 프로비저닝.
-> 동적을 더 추천하는 방시이다.
클러스터 관리자는 여러 PV를 만든다. 클러스터 사용자가 사용할 수 있는 실제 스토리지의 세부 사항을 제공한다. 이 PV들은 쿠버네티스 API에 존재하며 사용할 수 있다
정적 프로비저닝 = 관리자가 직접 pv를 만든다
kubetl get pv,pvc # 생성한 pv,pvc 확인
-> 용량, 접근모드, 반환 정책, 상태, pv가 어디에 부착되어있는지 확인 가능하다.
1 . pv 생성
echo "hello static nfs" | sudo tee /srv/nfs-volume/index.html
# nfs볼륨 생성 = 관리자 영역에 스토리지(nfs) 구축
apiVersion: v1
kind: PersistentVolume
metadata:
name: myapp-pv-nfs
spec:
capacity:
storage: 1Gi # 용량 설정
accessModes: # 접근모드 설정
- ReadWriteMany # RWX(= 읽기,쓰기,many)
persistentVolumeReclaimPolicy: Retain # 반환정책 설정
nfs:
path: /srv/nfs-volume # 관리자영역에서 생성한 nfs볼륨 설정
server: 192.168.56.11 # 관리자영역 설정
-> 관리자 영역에서 생성한 nfs볼륨을 기타설정과 함께 pv생성 및 연결한다
1-1 . pvc생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc-nfs
spec:
accessModes:
- ReadWriteMany #
resources:
requests:
storage: 1Gi # pv가 가진 capacity중 특정 자원만큼 요청
volumeName: myapp-pv-nfs # pvc와 pv를 연결(= bind)
storageClassName: '' # 정적에서는 지정 x
1-2 . 파드 생성
apiVersion: apps/v1
kind: ReplicaSet
metadata:
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:
claimName: myapp-pvc-nfs # pvc와 파드 연결
1-3 . 서비스 생성
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-nfs
spec:
type: LoadBalancer
ports:
- port: 80
targetPort: 80
selector:
app: myapp-rs-nfs
-> pv와 pvc, 파드와 서비스를 생성 한 후 curl을 통해 볼륨이 마운트되서 링크된 것을 확인
-> pvc에 연결되어 있는 파드들을 삭제 한 후 다시 파드를 생성해도 마찬가지로 링크되어 curl이 보내지는 것을 확인
-> 만약 pvc를 삭제하면 pv와의 연결이 끊어지고 다시 pvc를 생성해도 절대 pv와 다시 연결되지 않는다.
= 한번 bind된 pv는 더이상 pvc와 연결을 하지 못한다. 즉, pv는 재사용이 불가능하다.
📒 NFS Subdir External Provisioner설치 방법 참조
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
kubectl apply -k .
1 . pvc생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc-dynamic
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs-client # 스토리지 클래스 이름 지정
-> 정적 프로비저닝과 다르게 스토리지 클래스 네임을 지정한다.
-> 스토리지 클래스 네임 : pv를 만들기 위한 프로파일
-> pvc를 생성하면 pv가 자동으로 같이 생성되서 바인드되는 것을 알 수 있다.
1-1 . 파드 생성
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
2 . default 스토리지 클래스
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myapp-pvc-dynamic
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
# 스토리지 클래스 이름 지정 x
-> 스토리지 클래스 이름을 지정하지 않고 사용
-> 대신 기존의 설치한 스토리지 클래스를 default 스토리지 클래스로 지정해서 일일이 스토리지 클래스를 지정하지 않고도 사용이 가능해진다
-> default 스토리지 클래스는 하나만 지정이 가능하다
kubectl patch storageclasses.storage.k8s.io nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
# kubectl edit sc sc이름 으로도 수정 가능
-> default 스토리지 클래스 적용
컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다. 파드는 볼륨에서 환경 변수, 커맨드-라인 인수 또는 구성 파일로 컨피그맵을 사용할 수 있다.
컨피그맵을 사용하면 컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.
kubectl get cm # configmap확인
1 . 키/벨류를 이용한 config맵 생성
kubectl create cm my-config1 --from-literal=key1=value1
# 키:벨류값이 들어간 컨피그맵 생성
kubectl describe cm my-config1
kubectl create cm my-config2 --from-literal=key1=value1 --from-literal=key2=value2
# 여러 키:벨류값이 들어간 컨피그맵 생성
kubectl describe cm my-config1
2 . 파일명을 지정한 config맵 생성
echo value3 > key3
# 파일 생성
kubectl create cm my-config3 --from-file=key3
# 파일명을 지정한 컨피그맵 생성
kubectl describe cm my-config3
# 파일명이 키값이 되고, 벨류값으로 파일의 내용이 들어간다
kubectl create cm my-config4 --from-file=key3-tree=key3
# 파일명을 지정하되 다른 이름으로 지정
kubectl describe cm my-config4
# 지정한 파일명이 키값이 되고 벨류값으로 파일 내용이 들어간다
3 . yaml파일로 config맵 생성
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config4
data:
key4: value4 # 키:벨류 작성
4 . config맵 사용하기
#볼륨을 이용한 컨피그맵 참조
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: mypod
image: redis
volumeMounts:
- name: foo # 볼륨 마운트
mountPath: "/etc/foo"
readOnly: true
volumes:
- name: foo # 컨피그맵을 참조한 볼륨 생성
configmap:
name: myconfigma
-> 주로 위의 방식처럼 볼륨을 이용한 config맵 참조를 한다
-> 보통 기업에서는 도커허브의 퍼블릭 레포지터리같은 퍼블릭 이미지는 사용하지 않는다(보안 위험). 따라서 주로 프라이빗 레포지터리에서 이미지를 사용하는데 이것은 인증을 받아야 이용할 수 있다. 매번 인증하는 것은 비효율적이므로 인증서를 등록해 인증을 받는다.
인증서는 시크릿을 통해 제작할 수 있다.
1 . Opaque타입의 시크릿 생성
kubectl create secret generic my-secret --from-literal=key1=value1
# 임의의 값으로 시크릿 생성
kubectl get secret
# 생성한 시크릿 정보 보기
kubecrtl describe secret my-secret
# 컨피그맵과 다르게 벨류값을 보여주지 않는다
kubectl get secret my-secret -o yaml
-> yaml형식으로 보면 key값이 암호화된거처럼 보인다.
-> 사실 이것은 암호화가 아닌 인코딩 된거 뿐이고 실제로 이것을 디코딩 해보면 쉽게 값을 얻을 수 있다.
-> 즉, 쿠버네티스에서는 시크릿을 통해서 etcd에 저장할때 인코딩과 보여주지 않을 뿐 암호화를 통해 저장하는 것이 아니다.
-> 따라서 실제로 시크릿을 암호화 하기 위해서는 암호화 소프트웨어와 함께 사용하여야 한다
echo dmFsdWUx | base64 -d
# 디코딩을 통해 실제 값 보기
📒 명령어를 통해 시크릿을 생성할때는 벨류값이 자동으로 인코딩되지만, yaml파일로 시크릿을 생성할때는 자동으로 인코딩되지 않기 때문에 반드시 인코딩된 값을 넣어주어야 한다.
2 . 자체 서명 인증서 생성 예시
letsencrypt or 가비아 같은데서 CA를 통해 인증서를 받아야하지만 도메인이 있어야하므로 자체 서명 인증서를 생성한다.
mkdir nginx-tls/
# 키 및 인증서 보관할 디렉터리 생성
openssl genrsa -out nginx-tls/nginx-tls.key 2048
# 키 생성
openssl req -new -x509 -key nginx-tls/nginx-tls.key -out nginx-tls/nginx-tls.crt -days 365 -subj /CN=myapp.example.com
# 인증서 생성
base64 nginx-tls.crt -w 0
# 인증서 인코딩
base64 nginx-tls.key -w 0
# 키 인코딩
2-1 . 시크릿 생성
apiVersion: v1
kind: Secret
metadata:
name: nginx-tls-secret
type: kubernetes.io/tls
data:
tls.crt: # 인코딩한 인증서 복사
tls.key: # 인코딩한 키 복사
2-2 . 파드 생성
apiVersion: v1
kind: Pod
metadata:
name: nginx-pod-https
labels:
suhwan: good
spec:
containers:
- image: nginx
name: nginx-https
volumeMounts:
- name: nginx-tls-config
mountPath: /etc/nginx/conf.d
- name: https-cert
mountPath: /etc/nginx/ssl
readOnly: true
ports:
- containerPort: 80
protocol: TCP
- containerPort: 443
protocol: TCP
volumes:
- name: nginx-tls-config
configMap:
name: nginx-tls-config
- name: https-cert
secret:
secretName: nginx-tls-secret
-> 생성한 컨피그맵과 시크릿을 볼륨으로 만들어 마운트한다
-> 외부에서 접속하기 위해 label을 서비스의 label과 일치
2-3 . 컨피그맵 생성
apiVersion: v1
kind: ConfigMap
metadata:
name: nginx-tls-config
data:
nginx-tls.conf: |
server {
listen 80;
listen 443 ssl;
server_name myapp.example.com;
ssl_certificate /etc/nginx/ssl/tls.crt;
ssl_certificate_key /etc/nginx/ssl/tls.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
2-4 . 서비스 생성
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: LoadBalancer
ports:
- name: http
port: 80
targetPort: 80
- name: https
port: 443
targetPort: 443
selector:
suhwan: good
-> 로드밸런서의 외부용 ip를 브라우저에 입력했을 때 https로 통신이 되는 것을 확인
1 . 인증서 생성
mkdir ingress-tls/
# 키와 인증서 보관할 디렉터리 생성
openssl genrsa -out ingress-tls/ingress-tls.key 2048
# 키 생성
openssl req -new -x509 -key ingress-tls/ingress-tls.key -out ingress-tls/ingress-tls.crt -days 365 -subj /CN=myapp.example.com
# 인증서 생성
base64 ingress-tls.crt -w 0
# 인증서 인코딩
base64 ingress-tls.key -w 0
# 키 인코딩
2 . 시크릿 생성
apiVersion: v1
kind: Secret
metadata:
name: myapp-tls-secret
type: kubernetes.io/tls
data:
tls.crt: # 인코딩한 인증서 복사
tls.key: # 인코딩한 키 복사
3 . 인그레스 생성
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ing-tls-term
spec:
tls:
- secretName: myapp-tls-secret # 생성한 시크릿
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp-svc-np # 내부에서 서비스와 연결
port:
number: 80
4 . 서비스 생성
apiVersion: v1
kind: Service
metadata:
name: myapp-svc-np
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
nodePort: 31111
selector:
app: myapp-rs
5 . 파드 생성
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myapp-rs
spec:
replicas: 3
selector:
matchLabels:
app: myapp-rs
template:
metadata:
labels:
app: myapp-rs
spec:
containers:
- name: myapp
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080