[쿠버네티스] 볼륨(hostPath) & 퍼시스턴트 볼륨 & ConfigMap & 시크릿

신현식·2023년 2월 20일
0

구름_Kubernetes

목록 보기
6/25
post-thumbnail

hostPath

hostPath 볼륨은 호스트 노드의 파일시스템에 있는 파일이나 디렉터리를 파드에 마운트한다. 이것은 대부분의 파드들이 필요한 것은 아니지만, 일부 애플리케이션에 강력한 탈출구를 제공한다.
네트워크 기반의 스토리지가 아니기때문에 네트워크를 넘어서서 있는 컴퓨팅 리소스에 볼륨을 제공할 수 없다. 해당되는 볼륨을 자기 자신에게 있는 호스트에서 찾는다.

HostPath 볼륨에는 많은 보안 위험이 있으며, 가능하면 HostPath를 사용하지 않는 것이 좋다. HostPath 볼륨을 사용해야 하는 경우, 필요한 파일 또는 디렉터리로만 범위를 지정하고 ReadOnly로 마운트해야 한다.

# 레플리카셋 파일
vi myapp-rs-hp.yaml

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

# 서비스 파일
vi myapp-svc-hp.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-hp
spec:
  type: LoadBalancer
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: myapp-rs-hp



# 노드 1에서 파일경로 생성
ssh kube-node1

# 파일경로에 index.html 파일 생성
sudo mkdir /srv/web-contents
echo "hello node1" | sudo tee /srv/web-contents/index.html
exit

# control-plane에서 진행
kubectl create -f myapp-rs-hp.yaml
kubectl create -f myapp-svc-hp.yaml

kubectl get -pods -o wide
  • 네트워크가 안되거나, 이미지를 pull할 수 없거나, 하면 파일 경로가 존재하지 않아 마운트되지 않되어 볼륨에 연결이 안되는 등의 이유때문에 컨테이너가 작동하지 않으면 로그도 존재하지 않는다. 지금은 노드2,3에 파드가 만들어졌기 때문에 작동하지 않는 것이다. 만약 노드1에 만들어졌다면 정상적으로 작동했을 것이다.
# 노드 2에서 파일경로 생성
ssh kube-node2
sudo mkdir /srv/web-contents
echo "hello node2" | sudo tee /srv/web-contents/index.html
exit

# 노드 3에서 파일경로 생성
ssh kube-node2
sudo mkdir /srv/web-contents
echo "hello node3" | sudo tee /srv/web-contents/index.html
exit

# 이전에 존재한 파드 삭제
kubectl delete pod 파드이름 (--force)

# 연결이 되는지 확인
kubectl get svc
curl 서비스IP
  • 지금은 파드가 생성된 노드들에 경로를 만들었기 때문에 마운트가 잘 연결되고 컨테이너가 잘 실행되는 것을 확인할 수 있다. 또한 이전에 있던 파드들은 연결이 되지 않은 상태로 끝이기에 종료를 시켜준 이후 다시 새로운 파드를 실행시켜줘야한다.

nfs

nfs 볼륨을 사용하면 기존 NFS (네트워크 파일 시스템) 볼륨을 파드에 마운트 할수 있다. 파드를 제거할 때 지워지는 emptyDir 와는 다르게 nfs 볼륨의 내용은 유지되고, 볼륨은 그저 마운트 해제만 된다. 이 의미는 NFS 볼륨에 데이터를 미리 채울 수 있으며, 파드 간에 데이터를 공유할 수 있다는 뜻이다. NFS는 여러 작성자가 동시에 마운트할 수 있다.

nfs 설치 및 설정

cd ~
sudo apt install nfs-kernel-server
sudo mkdir /srv/nfs-volume

# exports 파일 생성
echo "/srv/nfs-volume *(rw,sync,no_subtree_check,no_root_squash)" | sudo tee /etc/exports
cat /etc/exports

# export 시킴
sudo exportfs -arv

# 모든 노드에 nfs-common 패키지 설치
ansible kube_node -i ~/kubespray/inventory/mycluster/inventory.ini -m apt -a 'name=nfs-common' --become
  • 노드들이 NFS를 마운팅을 하기 떄문에 파드가 아닌 노드가 마운트해야함

퍼시스턴트 볼륨

지금까지는 볼륨과 관련된 설정을 파드 내부에서 했다. 이는 파드를 삭제하면 볼륨도 삭제되는 것(데이터는 별개)과 동일하다. 하지만 스토리지 관리는 컴퓨트 인스턴스 관리와는 별개의 문제다.
퍼시스턴트볼륨 서브시스템은 사용자 및 관리자에게 스토리지 사용 방법에서부터 스토리지가 제공되는 방법에 대한 세부 사항을 추상화하는 API를 제공한다. 이를 위해 퍼시스턴트볼륨 및 퍼시스턴트볼륨클레임이라는 두 가지 새로운 API 리소스를 소개한다.

  • 퍼시스턴트볼륨 (PV)은 관리자가 프로비저닝하거나 스토리지 클래스를 사용하여 동적으로 프로비저닝한 클러스터의 스토리지이다. 노드가 클러스터 리소스인 것처럼 PV는 클러스터 리소스이다. PV는 Volumes와 같은 볼륨 플러그인이지만, PV를 사용하는 개별 파드와는 별개의 라이프사이클을 가진다. 이 API 오브젝트는 NFS, iSCSI 또는 클라우드 공급자별 스토리지 시스템 등 스토리지 구현에 대한 세부 정보를 담아낸다.

  • 퍼시스턴트볼륨클레임 (PVC)은 사용자의 스토리지에 대한 요청이다. 파드와 비슷하다. 파드는 노드 리소스를 사용하고 PVC는 PV 리소스를 사용한다. 파드는 특정 수준의 리소스(CPU 및 메모리)를 요청할 수 있다. 클레임은 특정 크기 및 접근 모드를 요청할 수 있다(예: ReadWriteOnce, ReadOnlyMany 또는 ReadWriteMany로 마운트 할 수 있음.

구조: pod - PVC -(개발자 영역, 파드의 리소스) l (관리자 영역, 볼륨의 리소스) - PV - NFS

  • PV가 실제 스토리지를 정의, 파드에서는 PVC라는 리소스를 만들어 연결시켜주고 PVC는 PV와 연결시켜줌,
  • 레이블과 레이블 셀렉터를 이용하거나 이름을 직접 사용하여 PV와 PVC를 연결함

PV & PVC의 생명주기

  • 프로비저닝: PV를 생성
  • 바인딩: PVC를 생성해서 PV와 연결
  • 사용: 연결이 되면 사용
  • 반환: 사용자가 볼륨을 다 사용하고나면 리소스를 반환할 수 있는 API를 사용하여 PVC 오브젝트를 삭제

프로비저닝

PV를 프로비저닝 할 수 있는 두 가지 방법이 있다.

  • 정적(static) 프로비저닝과 동적(dynamic) 프로비저닝

정적 프로비저닝

클러스터 관리자는 여러 PV를 만든다. 클러스터 사용자가 사용할 수 있는 실제 스토리지의 세부 사항을 제공한다. 이 PV들은 쿠버네티스 API에 존재하며 사용할 수 있다.

pv.spec.nfs = pod.sepc.volumes.nfs 파드에 직접 설정하는지 PV에 설정하는지만 다름

# pv 파일
vi myapp-pv-nfs.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: myapp-pv-nfs
spec:      # nfs는 기본적으로 용량이 없지만 이 옵션이 없으면 안만들어지기때문에 넣어줘야함
  capacity:
    storage: 1Gi
  accessModes:       # 접근제어
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain   # static이기에 지정안해도 상관없음
  nfs:
    path: /srv/nfs-volume
    server: 192.168.56.11


# pvc 파일
vi myapp-pvc-nfs.yaml  

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myapp-pvc-nfs
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  volumeName: myapp-pv-nfs     # 볼륨의 이름으로 지정해서 사용
  storageClassName: ''         # 동적 프로비저닝에서 사용, 따라서 빈칸
  • pvc는 pv를 요청하는 것이기에 pv에서 허용된 accessModes 중에 골라서 선택한다.
# 레플리카셋 파일
vi myapp-rs-nfs.yaml

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이름만 지정해주면 됨

# 서비스 파일
vi 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
# pv 생성
kubectl create -f myapp-pv-nfs.yaml

# 현재 pv의 상태를 보면 Available
kubectl get pv
# pvc 생성
kubectl create -f myapp-pvc-nfs.yaml

# pv,pvc 확인, pvc를 생성한 후 보면 상태가 pv와 pvc 모두 Bound(연결됨)로 바뀜
kubectl get pv
kubectl get pvc

# 파드 및 서비스 생성
kubectl create -f myapp-rs-nfs.yaml
kubectl create -f myapp-svc-nfs.yaml

# 서비스의 IP 확인
kubectl get svc
curl 서비스의 IP
  • 파드를 지우고 pv와 pvc만 남았을때 다시 파드를 생성하면 기존에 있던 것들을 사용할 수 있다. 하지만 파드와 pvc를 지우고 pv만 남겼을 때, pv를 상태를 확인해보면 (kubectl get pv) 상태가 Released인 것을 볼 수 있다.
    이후 파드와 pvc를 새로 만들고 pv와 pvc를 확인해보면 pv는 똑같이 Released이고 pvc는 Pending상태를 계속 유지하는 것을 볼 수 있다. 즉 PV는 재사용이 불가능하다는 것을 알 수 있다.

💡 tee (ex. echo "hello static nfs" | sudo tee /srv/nfs-vloume/index.html)

cat과 비슷한 명령으로 입력을 출력으로 바꿔준다. 앞에있는 출력을 뒤에있는 파일의 입력으로 받는다고 생각하면 됨.

동적 프로비저닝

관리자가 생성한 정적 PV가 사용자의 퍼시스턴트볼륨클레임과 일치하지 않으면 클러스터는 PVC를 위해 특별히 볼륨을 동적으로 프로비저닝 하려고 시도할 수 있다. 이 프로비저닝은 스토리지클래스를 기반으로 한다. PVC는 스토리지 클래스를 요청해야 하며 관리자는 동적 프로비저닝이 발생하도록 해당 클래스를 생성하고 구성해야 한다. "" 클래스를 요청하는 클레임은 동적 프로비저닝을 효과적으로 비활성화한다.
PV를 자동으로 만들어 준다고 생각하면 된다.

스토리지 클래스

쉽게 말해서 PV를 만들기 위한 프로파일이다. PV를 만들기 위한 정보를 가지고 있는 리소스가 스토리지 클래스라는 리소스이다.

쿠버네티스에는 내장 NFS 프로비저너가 없다. NFS를 위한 스토리지클래스를 생성하려면 외부 프로비저너를 사용해야 한다. NFS를 위한 외부 프로비저너

# NFS를 위한 외부 프로비저너 클론
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git
cd nfs-subdir-external-provisioner/deploy/

# 컨테이너 내용 수정
vi deployment.yaml

..
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 공유 디렉터리의 경로로 수정

# 배포(kustomization 파일과 함께 디렉터리 내에서 실행)
kubectl apply -k .

kubectl patch storageclasses.storage.k8s.io nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

# 현재 설치되어 있는 것들을 지우면 안됨. 동적 프로비저닝을 구성하는 것들임
kubectl get all


cd ~/goorm-8th-k8s/manifests/06_storage/06_dynamic/


vi myapp-pvc-dynamic.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myapp-pvc-dynamic
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs-client       # 스토리지 클래스의 이름 지정 -> 동적 프로비저닝
  
# 레플리카셋 파일
vi 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  


# pvc 생성
kubectl create -f myapp-rs-dynamic.yaml

# pvc, pv 확인, pv를 만들지 않았지만 동적으로 만들어진 것을 확인
kubectl get pvc
kubectl get pv
  • pvc를 지우게 되면 자동으로 pv도 사라진다.

  • 스토리지 클래스를 선언하지 않는 것(#storageClassName: nfs-client)과 명시적으로 스토리지 클래스가 없다라고 하는 것(storageClassName:"")은 큰 차이가 있다. 후자는 명시적으로 동적 프로비저닝을 사용하지 않겠다라는 의미이고 전자는 스토리지 클래스의 이름만 지정하지 않았을 뿐 여전히 동적 프로비저닝을 사용한다는 의미이다.

동적 볼륨 프로비저닝(default storageclass로 지정)

스토리지 클래스가 지정되지 않은 경우 모든 클레임이 동적으로 프로비전이 되도록 클러스터에서 동적 프로비저닝을 활성화 할 수 있다.

  • 하나의 StorageClass 오브젝트를 default 로 표시한다.
  • API 서버에서 DefaultStorageClass 어드미션 컨트롤러를 사용하도록 설정한다.

관리자는 storageclass.kubernetes.io/is-default-class 어노테이션을 추가해서 특정 StorageClass 를 기본으로 표시할 수 있다.

클러스터에는 최대 하나의 default 스토리지 클래스가 있을 수 있다. 그렇지 않은 경우 storageClassName 을 명시적으로 지정하지 않은 PersistentVolumeClaim 을 생성할 수 없다.

# 스토리지클래스 확인
kubectl get sc

# 위에서 스토로지명으로 확인, 어노테이션 추가 
kubectl edit sc nfs-client

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"     # 추가한 내용
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"nfs-client"},"parameters":{"archiveOnDelete":"false"},"provisioner":"k8s-sigs.io/nfs-subdir-external-provisioner"}
  creationTimestamp: "2023-02-20T08:22:43Z"


# 이 명령으로도 가능
kubectl patch storageclasses.storage.k8s.io nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

접근 모드

리소스 제공자가 지원하는 방식으로 호스트에 퍼시스턴트볼륨을 마운트할 수 있다. 각 PV의 접근 모드는 해당 볼륨에서 지원하는 특정 모드로 설정된다. 예를 들어 NFS는 다중 읽기/쓰기 클라이언트를 지원할 수 있지만 특정 NFS PV는 서버에서 읽기 전용으로 export할 수 있다.

모든 스토리지에서 ReadWriteMany(RWX)가 되지 않기 때문에 4가지 접근모드를 전부 줄 수 없다.

  • ReadWriteOnce(RWO)
    하나의 노드에서 해당 볼륨이 읽기-쓰기로 마운트 될 수 있다. ReadWriteOnce 접근 모드에서도 파드가 동일 노드에서 구동되는 경우에는 복수의 파드에서 볼륨에 접근할 수 있다.

  • ReadOnlyMany(ROX)
    볼륨이 다수의 노드에서 읽기 전용으로 마운트 될 수 있다.

  • ReadWriteMany(RWX)
    볼륨이 다수의 노드에서 읽기-쓰기로 마운트 될 수 있다. nfs(파일 스토리지)는 가능하다.

  • ReadWriteOncePod(RWOP)
    볼륨이 단일 파드에서 읽기-쓰기로 마운트될 수 있다. 전체 클러스터에서 단 하나의 파드만 해당 PVC를 읽거나 쓸 수 있어야하는 경우 ReadWriteOncePod 접근 모드를 사용한다. 이 기능은 CSI 볼륨과 쿠버네티스 버전 1.22+ 에서만 지원된다.

반환(Reclaiming)

사용자가 볼륨을 다 사용하고나면 리소스를 반환할 수 있는 API를 사용하여 PVC 오브젝트를 삭제할 수 있다. 퍼시스턴트볼륨의 반환 정책은 볼륨에서 클레임을 해제한 후 볼륨에 수행할 작업을 클러스터에 알려준다. 현재 볼륨에 대한 반환 정책은 Retain, Recycle, 그리고 Delete가 있다. PV를 어떻게 처리할지에 대한 것이다.
연결되어 있는 상태에서 PV나 PVC를 먼저 지우는 것은 안되고 무조건 파드 먼저 지워야한다. 이 부분에서 이야기 하는 것은 파드를 지운 다음 PV와 PVC가 남아있을 때 이를 어떻게 처리할지에 대한 것이다.

Retain(보존)

Retain 반환 정책은 리소스를 수동으로 반환할 수 있게 한다. 퍼시스턴트볼륨클레임이 삭제되면 퍼시스턴트볼륨은 여전히 존재하며 볼륨은 "릴리스 된" 것으로 간주된다. 그러나 이전 요청자의 데이터가 여전히 볼륨에 남아 있기 때문에 다른 요청에 대해서는 아직 사용할 수 없다. 관리자는 다음 단계에 따라 볼륨을 수동으로 반환할 수 있다.(삭제해야한다.)

  1. 퍼시스턴트볼륨을 삭제한다. PV가 삭제된 후에도 외부 인프라(예: AWS EBS, GCE PD, Azure Disk 또는 Cinder 볼륨)의 관련 스토리지 자산이 존재한다.
  2. 관련 스토리지 자산의 데이터를 수동으로 삭제한다.
  3. 연결된 스토리지 자산을 수동으로 삭제한다.

동일한 스토리지 자산을 재사용하려는 경우, 동일한 스토리지 자산 정의로 새 퍼시스턴트볼륨을 생성한다.

Delete(삭제)

Delete 반환 정책을 지원하는 볼륨 플러그인의 경우, 삭제는 쿠버네티스에서 퍼시스턴트볼륨 오브젝트와 외부 인프라(예: AWS EBS, GCE PD, Azure Disk 또는 Cinder 볼륨)의 관련 스토리지 자산을 모두 삭제한다. 동적으로 프로비저닝된 볼륨은 스토리지클래스의 반환 정책을 상속하며 기본값은 Delete이다. 관리자는 사용자의 기대에 따라 스토리지클래스를 구성해야 한다. 그렇지 않으면 PV를 생성한 후 PV를 수정하거나 패치해야 한다. 퍼시스턴트볼륨의 반환 정책 변경을 참고하길 바란다.

Recycle(재활용)

Recycle 반환 정책은 더 이상 사용하지 않는다. 대신 권장되는 방식은 동적 프로비저닝을 사용하는 것이다. 재사용한다는 의미는 PV를 초기화한다는 것인데 이는 데이터를 전부 지우고 새롭게 만든다는 것이다. 스토리지마다 초기화하는 방법이 다르기 때문에 이 방법이 어렵다. 따라서 사용하지 않는 것이다.

그러나 관리자는 레퍼런스에 설명된 대로 쿠버네티스 컨트롤러 관리자 커맨드라인 인자(command line arguments)를 사용하여 사용자 정의 재활용 파드 템플릿을 구성할 수 있다. 사용자 정의 재활용 파드 템플릿에는 아래 예와 같이 volumes 명세가 포함되어야 한다.

App_Custom

image

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-arg
spec:
  containers:
  - name: myapp
    image: ghcr.io/c1t1d0s7/go-myweb:alpine
    args:    # CMD를 변경함 -> mtweb -port=8080으로 실행하도록 해줌
    - -port=8088
    ports:
    - containerPort: 8088
      protocol: TCP

env(환경변수)

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-env
spec:
  containers:
  - image: ghcr.io/c1t1d0s7/go-myweb:alpine
    name: myapp
    env:      # 변수를 할당가능
    - name: MESSAGE
      value: "Customized Hello World!"
    ports:
    - containerPort: 8080
      protocol: TCP

컨피그맵(ConfigMap)

컨피그맵은 키-값 쌍으로 기밀이 아닌 데이터를 저장하는 데 사용하는 API 오브젝트이다. 파드는 볼륨에서 환경 변수, 커맨드-라인 인수 또는 구성 파일로 컨피그맵을 사용할 수 있다.

컨피그맵을 사용하면 컨테이너 이미지에서 환경별 구성을 분리하여, 애플리케이션을 쉽게 이식할 수 있다.

# 쿠버네티스 생성시 생기는 cm 확인
kubectl describe cm kube-root-ca.crt

Data
====
ca.crt:  # key 부분
----    # 구분자
-----BEGIN CERTIFICATE-----    # value 부분



# 데이터 1개짜리 만들기
kubectl create configmap my-config1 --from-literal=key1=value1
kubectl describe cm my-config1
kubectl get cm

# 데이터 2개짜리 만들기
kubectl create configmap my-config2 --from-literal=key11=value1 --from-literal=1key22=value22
kubectl get cm
kubectl describe cm my-config2

# 파일로 생성, 키는 key3(파일명), value는 value3(파일 내용)이 들어감
echo value3 > key3
kubectl create cm my-config3 --from-file=key3


# 파일명을 지정가능(key-tree)
kubectl create cm my-config4 --from-file=key-tree=key3

# yaml 파일로 생성
vi my-config4.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: my-config4
data:
  key4: value4
  
kubectl create -f my-config4.yaml
kubectl describe cm my-config4

컨피그맵 실습

vi myapp-pod-cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-message
data:
  message: HELLO WORLD
---  
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod-cm
spec:
  containers:
  - image: ghcr.io/c1t1d0s7/go-myweb:alpine
    name: myapp
    env:
    - name: MESSAGE    # 변수명
      valueFrom:       # 값을 가져오는 곳 지정
        configMapKeyRef:
          name: myapp-message    # 위에 선언한 이름과 매핑
          key: message           # 위에 선언한 데이터와 매핑
    args: 
    - $(MESSAGE)
    ports:
    - containerPort: 8080
      protocol: TCP

# 파드와 컨피그맵이 생성됨
kubectl create -f myapp-pod-cm.yaml
# 창을 분할하여 1에서
kubectl port-forward myapp-pod-cm 8080:8080

# 2에서 접속하면 message로 생성했던 변수가 출력되는 것을 확인
curl localhost:8080


# 파일명만 key가 되고 경로는 되지 않는다.
kubectl create cm myapp-message --from-file=configmap/message
  • 컨피그 맵으로 설정 파일을 제공할 수 있다.

vi nginx-pod-compress.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-compress
spec:
  containers:
  - image: nginx
    name: nginx-compress
    volumeMounts:
    - name: nginx-compress-config
      mountPath: /etc/nginx/conf.d/
    ports:
    - containerPort: 80
      protocol: TCP
  volumes:
  - name: nginx-compress-config   # 볼륨 이름, 컨테이너에서 마운트 중
    configMap:         # 볼륨이 컨피그맵으로 구성됨
      name: nginx-gzip-config

# 컨피그맵 파일
vi nginx-cm-compress.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-gzip-config
data:
  nginx-gzip.conf: |     # key이름-> 파일명  | : 멀티라인
    server {
    listen              80;
    server_name         myapp.example.com;
    gzip on;                   # default는 비활성화이기에 활성화 시켜줘야댐
    gzip_types text/plain application/xml;   # 압축할 타입 지정
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
      }
    }
    
kubectl create -f nginx-cm-compress.yaml -f nginx-pod-compress.yaml
kubectl get pods

# 창을 분할하여 1에서
kubectl port-forward myapp-pod-cm 8080:80

# 2에서 접속
curl localhost:8080

# 상세정보 보기, 내용이 그대로 나온다는 것은 압축이 되지 않았다는 의미
curl localhost:8080 -I

# 클라이언트가 서버 측에 압축 요청
curl -H "Accept-Encoding: gzip" localhost:8080 -I
# 압축 된 것을 확인
curl -H "Accept-Encoding: gzip" localhost:8080

컨피그맵과 시크릿의 공통점

  • key:value라는 맵 딕셔너리를 저장가능
    컨피그맵은 평문으로 시크릿은 암호화로 저장

저장된 값참조방법
1. 쉘의 환경변수로 참조
2. 볼륨으로 참조(마운팅)

시크릿

시크릿은 컨피그맵과 유사하지만 특별히 기밀 데이터를 보관하기 위한 것이다.
개별 시크릿의 크기는 1 MiB로 제한된다. 이는 API 서버 및 kubelet 메모리를 고갈시킬 수 있는 매우 큰 시크릿의 생성을 방지하기 위함이다.

  • 시크릿 타입
kubectl create secret generic my-secret --from-literal=key1=value1
kubectl get secret
# value는 바로 보여주지 않음
kubectl describe secret my-secret

# 값을 보여주는데 여기서 나오는 값은 인코딩 된 값이다. 암호화X
kubectl get secret my-secret -o yaml

# 디코딩해서 확인하는 방법 base64: 인코딩, -d: 디코딩 
echo 인코딩값(dmFsdWUx) | base64 -d
-> value1
  • docker-registry: 프라이빗 저장소 이용방법
# 패스워드는 도커허브의 토큰을 넣는다. server 또한 여러가지로 사용가능 imagepullsecret를 지정하게되면 시크릿에 있는 것을 pull 할 수 있다. 프라이빗 저장소를 사용할 때 유용하다.
kubectl create secret docker-registry NAME --docker-username=user --docker-password=password --docker-email=email [--docker-server=string] [--from-file=[key=]source] [--dry-run=server|client|none] [options] 
  • tls
# 인증서와 키로 구성
kubectl create secret tls NAME --cert=path/to/cert/file --key=path/to/key/file
  • 야물파일을 만들때에는 직접 인코딩을 시켜서 그 값을 가지고 파일을 만들어야한다. 일반 값으로는 생성이 안된다.
# 시크릿 파일 구성
vi myuser-secret.yaml

apiVersion: v1
kind: Secret
metadata:
  name: myuser-secret
type: Opaque
data:
  username: YWRtaW4K     # admin
  password: UEBzc3cwcmQK  # P@ssw0rd
  
# 파일로 생성
kubectl create -f myuser-secret.yaml

# 인코딩 된 값 확인
kubectl describe secret myuser-secret -o yaml

nginx_https 구성


cd goorm-8th-k8s/manifests/07_app_custom/05_secret/03_nginx_https/conf

#  엔진엑스의 샘플 설정파일, tls통신을 위해 443을 추가해줘야함
vi nginx-tls.conf

server {
    listen              80;
    listen		443 ssl;     # https 사용을 위함
    server_name         myapp.example.com;
    ssl_certificate	/etc/nginx/ssl/tls.crt;      # 인증서 파일
    ssl_certificate_key	/etc/nginx/ssl/tls.key;  # key 파일
    ssl_protocols	TLSv1.2 TLSv1.3;
    ssl_ciphers		HIGH:!aNULL:!MD5;
    location / {
        root   /usr/share/nginx/html;
        index  index.html;
    }
}

# 현재 위치 03_nginx_https
cd ..

# 위 설정은 컴피그 맵으로 제공함
vi nginx-cm-https.yaml

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;
      }
    }

# 
vi nginx-secret-https.yaml

apiVersion: v1
kind: Secret
metadata:
  name: nginx-tls-secret
type: kubernetes.io/tls    # tls타입은 data가 정해져 있음
data:
  tls.crt:    # 인증서를 인코딩, base64 nginx-tls/nginx-tls.crt -w 0    
  tls.key:    # base64 nginx-tls/nginx-tls.key -w 0

# nginx 파드 만들기
vi nginx-pod-https.yaml

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod-https
  labels:
    app: nginx-https
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

# 서비스 생성
vi nginx-svc-https.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port:443
    tagetPort: 443
  selector:
    app: nginx-https

# 키 파일 생성
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      
  • 인증서 확인하는 방법 https 자물쇠 클릭 -> 이 연결은 안전합니다. -> 인증서가 유효함 클릭
# 한줄로 이어서 인코딩
base64 nginx-tls/nginx-tls.crt -w 0
base64 nginx-tls/nginx-tls.key -w 0

# 각각 나온 값들을 복사해서 위에 nginx-secret-https.yaml 파일의 각 위치마다 넣어줌


# 파드와 서비스 생성
kubectl create -f nginx-pod-https.yaml -f -f nginx-svc-https.yaml

# 엔드포인트와 파드, 서비스 구성 확인, 멀티포트이기에 엔드포인트가 2개임
kubectl get po,svc,ep

# 호스트 PC로 가서 접속해보면 http, https로 접속 가능
http://192.168.56.200
https://192.168.56.200
  • 서비스를 만들지 않고 테스트 하는 방법, 내부적으로 테스트하는 방법: port-forward
# 창1
kubectl port-forward nginx-pod-https 8443:443

# 창2, -k 옵선(in secure: 안전하다고 확인)을 빼면 자체서명 인증서이기에 접속이 안된다. 
curl -k httls://localhost:8443
  • 1024 이하의 포트는 열려면 관리자 권한이 필요하다. 따라서 이보다 큰 포트를 개방하여 사용한 것이다.

tls_termination_proxy

tls 종료 프록시 관련 설명
쿠버네티스에서는 매우 쉽게 구현가능하다.

  • 클라이언트 - 인터넷망 - 서비스 - 파드(nginx server)
    client에서 nginx서버까지 end to end로 https로 암호화가 걸린다. 문제는 서버 입장에서 서버가 정상인지 클라이언트가 검증한다는 것이다. 서버는 클라이언트를 확인하지 못한다. 그래서 서버입장에서는 클라이언트가 공격하는 것도 분간이 안되서 위험하다는 것이다.

  • tls_termination_proxy를 사용해서 클라이언트와 서버 사이에 프록시 서버(쿠버네티스 상에서는 인그레스)를 두어 https를 종료(암호 해제)시킨 후, 내부 프라이빗 네트워크 내에서는 평문통신을 한다.

cd ../04_tls_termination_proxy

# 인그레스 때문에 필요, 노드포트
vi myapp-svc-np.yaml

apiVersion: v1
kind: Service
metadata:
  name: myapp-svc-np
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 31111
  selector:
    app: myapp-rs

# 레플리카셋 파일, 직접 시크릿을 가지고 작동하지 않기 때문에 시크릿을 설정하지 않음
vi myapp-rs.yaml

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

# 시크릿 생성
vi myapp-secret-https.yaml

apiVersion: v1
kind: Secret
metadata:
  name: myapp-tls-secret
type: kubernetes.io/tls
data:
  tls.crt:   # base64 ingress-tls/ingress-tls.crt -w 0
  tls.key:   # base64 ingress-tls/ingress-tls.key -w 0
  

# 인그레스 파일
vi myapp-ing-tls-term.yaml

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ing-tls-term
spec:
  tls:
 # - hosts:              # 실습할때는 도메인을 없애는 것이 편함
 #   - myapp.example.com   
  - secretName: myapp-tls-secret  # 시크릿을 지정하면 여기있는 인증서와 키를 가져옴
  rules:
 # - host: myapp.example.com  # 실습할때는 도메인을 없애는 것이 편함
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend: 
          service:
            name: myapp-svc-np
            port: 
              number: 80
  • 암호화 통신은 클라이언트와 인그레스 사이에서만 진행되기 때문에 nginx 파드에서는 암호화를 위한 설정파일과 인증, 키가 필요없다.
# 현재 디렉터리 04_tls_termination_proxy

mkdir ingress-tls
openssl genrsa -out nginx-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 nginx-tls/nginx-tls.crt -w 0
base64 nginx-tls/nginx-tls.key -w 0

# 각각 나온 값들을 복사해서 위에 myapp-secret-https.yaml 파일의 각 위치마다 넣어줌

# 모든 파일 생성
kubectl creat -f .

# 내용 확인
kubrctl get po,rs
kubrctl get svc,ep
kubrctl get ing

# tls 적용 확인(인그레스 명 검색)
kubrctl describe ing myapp-ing-tls-term


# 호스트 PC에서 https로 접속, 3개 모두 마찬가지로 접속됨
https://192.168.56.21
https://192.168.56.22
https://192.168.56.23

# 명령어로 확인
curl -k https://192.168.56.21
curl -k https://192.168.56.22
curl -k https://192.168.56.23
profile
전공 소개

0개의 댓글