관리 메뉴

코딩 기록 저장소

[쿠버네티스] PART2. 쿠버네티스 기본 사용법 배우기 - ② 본문

개인 공부/쿠버네티스

[쿠버네티스] PART2. 쿠버네티스 기본 사용법 배우기 - ②

KimNang 2025. 4. 18. 08:49

목차

    1. 스테이트풀을 위한 영구 볼륨

    - 볼륨(Volume) : 데이터를 보관하는 저장소

    - 스테이트풀(stateful) : 데이터를 저장소에 저장해 두는 애플리케이션

    - 스테이스리스(stateless) : 어떠한 정보도 저장해 둘 필요가 없는 애플리케이션

    - 저장소를 사용하지 않는 스테이트리스는 단순히 디플로이먼트를 배포하면되기 때문에 구성 간단함

    - 스테이트풀은 저장소 사용해야 하기 때문에 구현 복잡함 

    01. 볼륨

    볼륨이란?

    - 데이터를 저장하는 저장소

    - 파드(컨테이너)가 종료되어도 데이터를 보존할 수 있음

    - 저장소에 볼륨을 생성한 후 파드(컨테이너)에 마운트하여 사용

    - 볼륨은 데이터가 저장되는 위치에 따라 세 가지로 분류할 수 있음

    • 데이터가 저장되는 위치
      • 파드 내에 위치
        - 파드가 종료될 때 데이터도 함께 삭제됨
      • 워커 노드 내에 위치
        - 파드가 종료되어도 데이터가 유지되지만, 노드가 종료되면 데이터도 삭제됨
      • 노드 외부에 위치
        - 파드 혹은 노드의 종료와 무관하게 데이터는 항상 보존됨

    - 볼륨의 라이프 사이클은 파드의 라이프사이클과 같음 (볼륨의 위치에 따라 의미가 조금씩 다름)

    - 파드가 생성되면 볼륨도 같이 생성되고, 파드가 삭제되면 볼륨도 같이 삭제됨 (파드 내에 데이터 저장시)

     

    볼륨 유형

    - 볼륨은 파드의 구성 요소이므로 컨테이너와 마찬가지로 매니페스트로 정의해 사용

    - 하지만 볼륨은 독립적인 쿠버네티스 리소스가 아니므로 자체적으로 생성되거나 삭제될 수 없음

    - 볼륨은 파드뿐만 아니라 파드 내의 모든 컨테이너에서 사용할 수 있으며 접근하려는 각 컨테이너에 마운트해야 함

    - 볼륨의 유형으로는 임시 볼륨, 로컬 볼륨, 외장 볼륨이 있음

    • 임시 볼륨 : 파드 내의 공간을 사용하는 것, 파드가 삭제(종료)되는 즉시 데이터도 함께 삭제됨
    • 로컬 볼륨 : 노드 내의 디스크를 저장소로 사용하는 것, 노드가 종료되는 즉시 데이터도 삭제됨
    • 외부 볼륨 : 노드의 외부에 있는 외부 저장소를 이용하는 것, 파드나 노드와는 무관하게 데이터를 영구적으로 사용할 수 있다는 장점은 있지만 외부 저장소가 따로 있어야 해서 비용을 고려해야 함
      임시볼륨 로컬 볼륨 외부 볼륨
      emptyDir hostPath NFS
      cephFS
      glusterFS
      iSCSI
      AWS EBS
      azureDisk

    emptyDir

    - 파드가 생성될 때 같이 생성되고 파드가 삭제될 때 같이 삭제되는 임시 볼륨

    - 파드가 생성될 때 빈 깡통처럼 생성된다고 해서 emptyDir이라고 부름

    - 파드가 삭제되면 emptyDir도 함께 삭제됨

    1. emptydir.yaml 파일을 생성함
      - 이 매니페스트는 하나의 파드에 nginx 컨테이너를 생성함
      - nginx 컨테이너에 emptyDir 볼륨인 /data/shared 디렉터리를 마운트함
      apiVersion: v1
      kind: Pod
      metadata:
        name: emptydata # emptydata라는 파드를 생성
      spec:
        containers:
        - name: nginx # nginx라는 이름의 컨테이너를 생성해 /data/shared 디렉터리 마운트
          image: nginx
          volumeMounts:
          - name: shared-storage
            mountPath: /data/shared # /data/shared 경로의 볼륨을 마운트
        volumes:
        - name: shared-storage
          emptyDir: {} # shared-storage라는 이름으로 emptyDir 기반의 볼륨을 생성


    2. yaml 파일을 실행해 파드를 생성하고 정보를 확인함
      kubectl apply -f emptydir.yaml
      kubectl get pods


    3. emptydata 파드에 접속한 후 /data/shared 디렉터리로 이동함
      - 파드에서 실행 중인 컨테이너의 셸을 가져오기 위해 kubectl exec -it [pod-name] - /bin/bash 형식의 명령어를 사용함
      kubectl exec -it emptydata -- /bin/bash # emptydata 파드에 접속
      cd /data/shared


    4. /data/shared 디렉터리에서 'hello'라는 내용을 갖는 test.txt 파일을 생성함
      - 생성된 파일을 확인하고 exit 명령어로 파드를 빠져나옴
       echo "hello" > test.txt # test.txt		# test.txt 파일 생성
       ls -al
       date;cat test.txt		# test.txt 파일의 내용 확인
       exit

     

    hostPath

    - 노드의 로컬 디스크를 파드에 마운트해서 사용하는 로컬 볼륨

    - 같은 hostPath를 사용하는 다수의 파드끼리 데이터를 공유해 사용할 수 있다는 장점이 있음

    - 파드가 삭제되어도 hostPath에 있는 파일들은 삭제되지 않고 남아 있으므로 같은 hostPath를 사용하는 다른 파드는 해당 볼륨에 접근해 파일들을 사용할 수 있음

    - 사용 중인 파드에 장애가 생겨 다른 노드에서 파드가 실행될 때는 hostPath 볼륨에 더 이상 접속할 수 없음

    1. hostpath.yaml 파일을 생성 후 정의함
      - 호스트의 /tmp 디렉터리를 컨테이너의 /data/shared 디렉터리에 마운트함
      apiVersion: v1
      kind: Pod
      metadata:
        name: hostpath
      spec:
        containers:
        - name: nginx
          image: nginx
          volumeMounts:
          - name: localpath
            mountPath: /data/shared # nginx라는 이름의 컨테이너에 /data/shared 디렉터리로 마운트
        volumes:
        - name: localpath
          hostPath:
            path: /tmp       # 워커 노드의 /tmp 디렉터리를 hostPath로 이용
            type: Directory


    2. 파드를 생성하고 정보를 확인함
      kubectl apply -f hostpath.yaml
      kubectl get pods


    3. hostpath 파드에 접속한 후 /data/shared 디렉터리로 이동함
      kubectl exec -it hostpath -- /bin/bash
      cd /data/shared


    4. /data/shared 디렉터리에서 'hello'라는 내용을 갖는 test.txt 파일을 생성함
      - 이후 생성된 파일을 확인하고 exit 명령어를 사용해 접속했던 파드를 빠져나옴
      echo "hello" > test.txt
      ls -al
      exit

     

     

     

    02. 영구 볼륨과 영구 볼륨 클레임

    영구 볼륨의 필요성

    - 디플로이먼트로 생성되는 파드는 '휘발성'이라는 특성을 가짐

    - 하지만 기업에서 운영하는 애플리케이션은 데이터를 저장소에 저장해서 사용하는 것들이 많음

    - 파드 내에 데이터를 저장하는 것이 아닌 외부에 위치하는 저장소에 데이터를 저장해둠

    - 데이터를 워커 노드(로컬 볼륨) 내에 저장할 수도 있지만, 노드가 종료되면 데이터도 함께 사라질 수 있어서 외부에 있는 저장소를 이용해야 함

     

    영구 볼륨과 영구 볼륨 클레임이란?

    - 영구 볼륨 (Persistent Volume, PV) : 컨테이너, 파드, 노드의 종료와 무관하게 데이터를 영구적으로 보관할 수 있는 볼륨
      시스템 관리자가 외부 저장소에서 볼륨을 생성한 후 쿠버네티스 클러스터에 연결하는 것을 의미함

    - 영구 볼륨을 요청하는 볼륨 클레임을 영구 볼륨 클레임(Persistent Volume Claim, PVC)이라고 함

    - 개발자가 요청한 영구 볼륨(영구 볼륨 클레임)은 API 서버에 보내지고 이후 API 서버를 통해 영구 볼륨이 할당됨

    - 개발자가 파드를 생성할 때 볼륨을 요청하는 영구 볼륨 클레임을 정의하면 볼륨이 자동으로 할당됨

    - 저장소의 볼륨은 시스템 관리자가 관리하면 되므로 역할을 분리할 수도 있음

     

    영구 볼륨과 영구 볼륨 클레임 사용하기

    - 파드에서 영구 볼륨 클레임 이름을 지정하며, 해당 이름을 갖는 영구 볼륨 클레임에서는 storageClassName을 이용해 영구 볼륨에 바인딩함

    - mysql 데이터베이스 파드에 영구 볼륨을 마운트

    - 외부 저장소가 없으니 노드의 디스크를 사용함. 외부 저장소를 사용하는 방법도 이와 유사함

    1. 영구 볼륨을 위한 pv.yaml 파일을 작성함
      apiVersion : v1
      kind : PersistentVolume
      metadata :
        name : mysql-pv-volume
        labels :
          type : local
      spec :
        storageClassName : manual    # 영구 볼륨 클레임의 요청을 해당 영구 볼륨에 바인딩
        capacity :
          storage : 20Gi	# 영구 볼륨으로 20Gi 할당
        accessModes :
          - ReadWriteOnce
        hostPath :
          path : "/mnt/data"	# 외부 저장소가 준비되지 않아서 노드의 /mnt/data 디렉터리를 볼륨으로 사용


    2. 작성한 yaml 파일을 kubectl apply 명령어를 이용해 영구 볼륨을 생성함
      kubectl apply -f pv.yaml


    3. 영구 볼륨 클레임 생성을 위해 pvc.yaml 파일을 작성함
      apiVersion : v1
      kind : PersistentVolumeClaim
      metadata :
        name : mysql-pv-claim
      spec :
        storageClassName : manual
        accessModes :
          - ReadWriteOnce
        resources :
          requests :
            storage : 20Gi


    4. 작성한 pvc.yaml 파일을 kubectl apply 명령어를 이용해 영구 볼륨 클레임을 생성
      kubectl apply -f pvc.yaml


    5. mysql 용도의 디플로이먼트(파드)를 생성하고 내용을 정의함
      - 데이터베이스 용도의 파드이기 때문에 영구 볼륨 클레임을 정의함
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: mysql # mysql이라는 디플로이먼트 생성
      spec:
        selector:
          matchLabels:
            app: mysql
        strategy:
          type: Recreate
        template:
          metadata:
            labels:
              app: mysql
          spec:
            containers:
            - image: mysql:8.0.29       # mysql 8.0.29 버전의 파드 생성
              name: mysql
              env:
              - name: MYSQL_ROOT_PASSWORD
                value: password         # mysql에 접속하기 위한 패스워드
              ports:
              - containerPort: 3306     # mysql에서 사용하는 포트
                name: mysql
              volumeMounts:
              - name: mysql-persistent-storage
                mountPath: /var/mysql  # /var/mysql 디렉터리로 볼륨 마운트
            volumes:
            - name: mysql-persistent-storage
              persistentVolumeClaim:   # 영구 볼륨 할당 요청
                claimName: mysql-pv-claim


    6. kubectl apply 명령어를 실행해 디플로이먼트를 생성함
      kubectl apply -f pvc-deployment.yaml


    7. 외부에서 접속하기 위한 서비스를 생성하기 위해 mysql_service.yaml이라는 파일을 작성함
      apiVersion : v1
      kind : Service
      metadata :
        name : mysql
      spec :
        clusterIP : None
        ports :
        - port : 3306
        selector :
          app : mysql


    8. 작성한 서비스 파일을 kubectl create 명령어를 실행하여 서비스를 생성
      kubectl create -f mysql_service.yaml


    9. 디플로이먼트, 서비스, 영구 볼륨, 영구 볼륨 클레임이 정상적으로 생성되었는지 확인
      • 디플로이먼트 상태 확인
        - 디플로이먼트에 문제가 있다면 error 라는 메시지가 보임
        - mysql이라는 이름의 컨테이너에 /var/mysql라는 디렉터리로 볼륨이 할당됐음을 볼 수 있음
        kubectl describe deployment mysql


      • 파드의 상태 확인
        kubectl get pods


      • 서비스 상태 확인
        - 명령어에서 service를 짧게 svc로 입력해도 인식함
        - mysql로 생성된 서비스의 포트가 3306인 것을 확인할 수 있음
        kubectl get service


      • 영구 볼륨 상태 확인
        - mysql-pv-volume이라는 이름으로 볼륨이 생성됨
        - 볼륨에 대한 자세한 정보는 kubectl describe pv mysql-pv-volume 명령어로 확인 가능
        kubectl get pv

        • RECLAIM POLICY
          - 영구 볼륨의 라이프 사이클
          - 영구 볼륨 클레임이 삭제될 때 영구 볼륨과 연결되어 있던 저장소에 저장된 파일을 어떻게 처리할지에 따라 나뉨
          Retain 정책 : 영구 볼륨 클레임이 삭제되어도 저장소에 저장되어 있던 파일을 삭제하지 않는 정책
          Delete 정책 : 영구 볼륨 클레임이 삭제되면 영구 볼륨과 연결된 저장소 자체를 삭제함
          Recycle 정책 : 영구 볼륨 클레임이 삭제되면 영구 볼륨과 연결된 저장소 데이터는 삭제

        • STATUS
          Available(사용가능) : 아직 클레임에 바인딩되지 않은 상태
          Bound(바인딩) : 볼륨이 클레임에 의해 바인딩된 상태
          Released(릴리즈) : 클레임이 삭제되었지만 클러스터에서 아직 리소스가 반환되지 않은 상태
          Failed(실패) : 볼륨이 자동 반환에 실패한 상태

      • 영구 볼륨 클레임에 대한 상태 확인
        - mysql-pv-claim이라는 이름의 영구 볼륨 클레임에서 사용하는 볼륨은 mysql-pv-volume이라는 것을 확인할 수 있음
        - 영구 볼륨 클레임에 대한 자세한 정보는 kubectl describe pvc mysql-pv-claim 명령어로 확인 가능
        kubectl get pvc

        • ACCESS MODES
          - 볼륨에 접근할 수 있는 모드를 나타냄
          RWO(ReadWriteOnce) : 하나의 노드에서 해당 볼륨이 읽기-쓰기로 마운트
          ROX(ReadOnlyMany) : 볼륨은 많은 노드에서 읽기 전용으로 마운트
          RWX(ReadWriteMany) : 볼륨은 많은 노드에서 읽기-쓰기로 마운트
          RWOP(ReadWriteOncePod) : 볼륨이 단일 파드에서 읽기-쓰기로 마운트. 즉, 전체 클러스터에서 단 하나의 파드만 영구 볼륨 클레임을 읽거나 쓸 수 있어야 하는 경우 ReadWriteOncePod에 사용

    10. mysql에 접속한 후 데이터베이스와 테이블을 생성함
      • mysql에 접속
        - 반응이 없다면 엔터 누름
        kubectl run -it --rm --image=mysql:latest --restart=Never mysql-client -- mysql -h mysql -ppassword


      • 데이터베이스 생성 및 선택
        CREATE DATABASE mySQL;
        
        use mySQL;


      • 테이블 생성
        CREATE TABLE Test
        (
          ID INT,
          Name VARCHAR(30) DEFAULT 'Anonymous',
          ReserveDate DATE,
          RoomNum INT
        );


      • 생성된 테이블 확인
        show tables;

    - mysql을 빠져나올땐 quit을 입력함

    - 데이터베이스와 테이블이 정상적으로 생성되었으며, 테이블에 값이 저장되어 있지 않지만 정상적으로 호출됨

    - 영구 볼륨이 정상적으로 동작하고 있음을 확인함

     

     

    2. 보안을 고려해 파드를 외부에 노출시키기

    01. 파드를 외부에 노출시키기

    - 파드의 특징 중 하나는 클러스터 내의 노드들로 옮겨 다님. 파드의 IP는 새로운 IP로 변경되기 때문에 외부에서 접근하기 어려움

    - 이를 해결하기 위해 서비스를 사용하여 파드가 클러스터 내 어디에 있든지 고정된 IP로 접근할 수 있게 함

    - 서비스는 클러스터 내부에서 접속하는 것과 외부에서 접속하는 방법에 따라 나뉨

    - 클러스터 내부 접속 용도의 서비스는 클러스터 내에 구성된 파드끼리 접속할 때 사용

    - 클러스터 외부 접속 용도의 서비스는 외부의 사용자가 내부 파드에 접근할 때 사용

    클러스터 내부 접속 용도 클러스터 외부 접속 용도
    ClusterIP ExternalName
    노드포트(NodePort)
    로드밸런서(LoadBalancer)
    인그레스(Ingress)

    - 노드포트나 로드밸런서, 인그레스 : 외부 → 내부 접근시 사용

    - ExternalName : 내부 → 외부 접근시 사용

     

    ClusterIP

    - 기본 서비스 타입, 클러스터 내 파드에 접근할 수 있는 가상의 IP

    - 서비스는 기본적으로 ClusterIP를 가짐

    - 서비스를 파드에 연결해 놓으면 서비스 IP(ClusterIP)를 통해서 파드에 접근할 수 있음

    - ClusterIP는 클러스터 내부에서만 사용할 수 있으며 외부에서는 파드에 접속할 수 없음

    1. ClusterIP.yaml 파일을 작성함
      - 매니페스트에서는 서비스 타입을 별도로 지정하지 않음
      - 서비스 유형을 지정하지 않으면 기본으로 ClusterIP를 사용하겠다는 의미
      apiVersion : apps/v1
      kind : Deployment
      metadata :
        name : clusterip-nginx
      spec :
        selector :
          matchLabels :
            run : clusterip-nginx
          replicas : 2
          template :
            metadata :
              labels :
                run : clusterip-nginx
            spec :
              containers :
              - name : clusterip-nginx
                image : nginx
                ports :
                - containerPort : 80


    2. kubectl apply 명령어를 이용해서 디플로이먼트(파드)를 생성함
      kubectl apply -f ClusterIP.yaml


    3. 생성된 파드의 상태를 확인함
      kubectl get pods -l run=clusterip-nginx -o wide

      • 명령어 옵션의 의미
        -l run=clusterip-nginx : clusterip-nginx라는 이름을 갖는 파드들의 정보를 보여줌. 이때 clusterip-nginx은 앞의 ClusterIP.yaml라는 파일에서 selector.run에 지정한 값
        -o wide : 파드의 상세 정보를 확인할 때 사용, --output=wide도 동일한 의미를 가짐
    4. 앞에서 생성한 clusterip-nginx 파드를 외부에 노출시킬 서비스를 생성함
      - 일반적으로 서비스를 생성하는 방법은 두 가지임
      ① 매니페스트를 이용해 생성하는 방법(yaml 파일 이용)
      ② kubectl expose 명령어를 사용하는 방법 ← 이 방법 사용
      kubectl expose deployment/clusterip-nginx


    5. 생성된 서비스의 상태와 정보를 확인
      - describe 사용시 서비스의 상세 정보 확인
      - 생성된 clusterip-nginx 서비스의 CLUSTERIP가 10.96.68.3임을 확인함
      kubectl get svc clusterip-nginx
      
      kubectl describe svc clusterip-nginx


    6. 파드의 엔드포인트(endpoint)를 확인
      - 엔드포인트를 조회한 결과에 값이 출력되었다면 클러스터 내의 다른 파드가 접속할 수 있도록 서비스가 노출되었다는 의미


    7. busybox 이미지를 이용해 파드를 하나 실행하고 파드 내에서 ClusterIP로 접속
      - busybox라는 이름의 파드를 하나 생성
      - 밑줄 표시된 부분을 입력해야 함
      kubectl run busybox --rm -it --image=busybox /bin/sh
      - <title>Welcome to nginx!</title>이 출력되었다면 ClusterIP를 이용해 정상적으로 콘텐츠를 가져온 것
      • 명령어 옵션의 의미
        • kubectl run busybox : busybox라는 이름으로 파드를 실행함
        • --rm : 컨테이너를 일회성으로 실행할 때 주로 사용
          컨테이너가 종료될 때 컨테이너와 관련된 리소스(파일 시스템, 볼륨)까지 제거
        • -it : -i 옵션과 -t 옵션은 같이 쓰이는 경우가 많음. 이 두 옵션은 컨테이너를 종료하지 않은 상태에서 터미널의 입력을 계속 컨테이너로 전달할 때 사용. -it 옵션은 컨테이너의 셸(shell)이나 CLI 도구를 사용할 때 유용함
        • --image=busybox : busybox라는 이미지를 이용해 파드를 생성
        • /bin/sh : 셸 스크립트를 사용할 때 사용

    ※ 서비스를 만들 때 ClusterIP를 별도로 지정하지 않으면(None으로 설정하면) ClusterIP가 없는 상태가 만들어짐

    헤드리스 서비스(headless service)라고 하며 주로 로드밸런싱이나 서비스 IP가 필요 없을 때 사용

    헤드리스 서비스에 셀렉터를 설정하면 API를 통해 접근할 수 있는 엔드포인트가 만들어지지만 셀렉터가 없으면 엔드포인트가 만들어지지 않음

     

     

    ExternalName

    - 조금 특별한 경우에 사용하는 서비스

    - 클러스터 내부에서 외부의 엔드포인트에 접속하기 위한 서비스

    - 엔드포인트는 클러스터 외부에 위치하는 데이터베이스나 애플리케이션의 API 등을 의미

    kind : Service
    apiVersion : v1
    metadata :
      name : external-service
    spec :
      type : ExternalName
      externalName : myservice.test.com		# external-service와 연결하려는 외부 도메인 값을 설정
    • 명령어의 의미
      • 외부 FQDN : myservice.test.com
        - FQDN(Fully Qualified Domain Name)은 도메인에 대한 전체 이름 표기 방식을 의미
        - www.a.com 혹은 web01.a.com 같은 형태가 FQDN
      • CNAME : external-service
        - 하나의 도메인 이름을 다른 이름으로 매핑시키는 역할을 하는 DNS 레코드의 일종

    - external-service라는 이름으로 요청이 들어온다면 클러스터 외부에 존재하는 myservice.test.com이라는 주소를 반환하는 것이 ExternalName 서비스

    - ExternalName 서비스는 프록시나 특정 이름(혹은 주소)을 다른 이름으로 자동으로 변환하는 리다이렉트 용도로 사용

     

     

    노드포트

    - 모든 워커 노드에 특정 포트(노드포트)를 열고 여기로 들어오는 모든 요청을 노드포트 서비스로 전달

    - 그런 후 노드포트 서비스는 해당 업무를 처리할 수 있는 파드로 요청을 전달함

    - 노드포트 타입의 서비스는 외부 사용자(혹은 애플리케이션)가 클러스터 내부에 있는 파드에 접속할 때 사용하는 서비스 중 하나

    - 노드포트는 30000~32767로 제한되어 있음

    - 어떤 서비스의 어떤 파드에 접속할지는 yaml파일에서 정의하여 사용함

    - 매니페스트를 이용해서 노드포트를 생성한다는 것은 노드포트 서비스를 하나 만들고 외부에서 어떤 포트로 접속할지를 지정하는 과정

    -내부의 어떤 파드를 사용할지 셀렉터로 지정하고 포트를 정의

    1. nginx-deployment.yaml 파일로 디플로이먼트 생성하고 파일 작성
      apiVersion : apps/v1
      kind : Deployment
      metadata :
        name : nginx-deploy
        labels :
          app : nginx
      spec :
        replicas : 3
        selector :
          matchLabels :
            app : nginx
        template :
          metadata :
            labels :
              app : nginx
          spec :
            containers :
            - name : nginx
              image : nginx:latest
              ports :
              - containerPort : 80


    2. kubectl apply 명령어로 디플로이먼트를 생성함
      kubectl apply -f nginx-deployment.yaml


    3. 디플로이먼트의 상태 확인
      - yaml 파일에서 파드 3개를 생성하도록 작성했으므로 READY에서 3/3의 결과가 출력됨


    4. 파드의 정보 가져옴
      - 3개의 파드는 모두 다른 이름을 갖는데, 'nginx-deployment+임의의 숫자/문자 조합' 형태로 파드의 이름이 구성됨
      - 현재 3개의 파드가 모두 정상 상태임을 확인


    5. 생성한 파드를 외부에 노출시키기 위해 서비스 오브젝트를 생성함
      - nginx-svc.yaml이라는 이름으로 생성해 둔 파일이 있다면 kubectl delete -f nginx-svc.yaml, rm nginx-svc.yaml를 차례대로 실행해 기존 파일을 삭제한 후 진행함
      - nginx-svc.yaml 파일로 생성한 서비스는 my-nginx라는 이름을 가지며 외부에서 접속할 때 사용할 서비스의 노드포트는 별도로 지정하지 않아서 임의로 부여
      apiVersion : v1
      kind : Service
      metadata :
        name : my-nginx
        labels :
          run : my-nginx
      spec :
        type : NodePort		# 외부에서 접속할 때 노드포트 서비스 사용
        ports :
        - port : 8080		# 노드포트 서비스에서 사용하는 포트
          targetPort : 80		# 파드(컨테이너)에서 사용하는 포트
          protocol : TCP
          name : http
        selector :
          app : nginx		# nginx라는 이름의 레이블을 갖는 파드를 서비스


    6. my-nginx의 서비스 포트를 확인해야 함
      - 노드포트는 서비스를 통해 외부에 노출되기 때문에 서비스의 포트를 확인해야 함
      kubectl get svc | grep my-nginx


    7. 노드포트를 이용해 서비스를 외부에 노출했기 때문에 노드(호스트)의 IP에 뒤에 31899포트를 붙여서 접속할 수 있음

     

    • 노드포트의 몇 가지 제약사항
      • 포트당 하나의 서비스만 사용할 수 있음
      • 30000에서 32767까지의 포트만 사용할 수 있음. 해당 범위를 넘는 서비스를 운영 중이라면 사용이 불가능함
      • 노드나 가상 머신의 IP 주소가 바뀌면 반드시 반영해야 함

     

     

    로드밸런서

    - 서버에 가해지는 부하(=로드)를 분산(=밸런싱)해주는 장치 또는 기술을 통칭

    - 사용자가 한 대의 서버(혹은 파드)에 집중해 접근한다면 그 서버(파드)는 부하를 견디지 못하고 비정상적으로 종료될 수 있음

    - 이러한 현상이 발생하지 않도록 부하를 고루 분산하기 위해 사용하는 것이 로드밸런서

    1. L4 로드밸런서
      - OSI 7계층에서 네트워크 계층(IP, IPX)이나 트랜스포트 계층(TCP, UDP)의 정보를 바탕으로 로드를 분산함
      - IP 주소나 포트 번호 등을 이용해 트래픽을 분산하는 것
    2. L7 로드밸런서
      - OSI 7계층에서 애플리케이션 계층(HTTP, FTP, SMTP)을 기반으로 로드를 분산하기 때문에 URL, HTTP 헤더, 쿠키 등과 같은 사용자의 요청을 기준으로 트래픽을 분산
      - L7 로드밸런서의 기능은 인그레스를 사용해 구현가능
      - 인그레스는 외부에서 파드에 접속할 때 부하 분산 용도로 사용

    로드밸런서 실습
    - 보통 L4 스위치를 사용하는 로드밸런서는 외부에서 접근할 수 있는 IP를 이용해 로드밸런서를 구성함

    - 외부 IP(공인 IP)를 로드밸런서로 설정하면 클러스터 외부에서 접근할 수 있음

    1. httpd 이미지를 이용해 디플로이먼트를 생성함
      - 지금까지 yaml 파일을 통해 디플로이먼트를 생성했지만 이미지를 이용해 간단하게 디플로이먼트를 생성할 수도 있음
      kubectl create deployment httpd --image=httpd


    2. 외부 IP를 이용해 httpd를 외부에 노출하기 위한 서비스를 생성
      - 외부 IP를 이용하는 방법은 externalIPs에 외부 IP를 입력해주면 됨
      - httpd 서비스를 외부에 노출하기 위해 externalIPs에 워커 노드의 IP를 지정함
      cat <<EOF > httpd-service.yaml
      apiVersion: v1
      kind: Service
      metadata:
        name: httpd-service
      spec:
        selector:
          app: httpd
        ports:
          - name: http
            protocol: TCP
            port: 80
            targetPort: 80
        externalIPs:
          - 172.30.1.44
      EOF


    3. yaml 파일을 이용해 서비스를 생성함
      kubectl create -f httpd-service.yaml


    4. 생성된 서비스 확인
      - 외부 IP(EXTERNAL-IP)가 생성된 것을 확인할 수 있음


    5. curl 명령어를 사용해 httpd 파드에 접속함
      - httpd 서비스에 대한 기본 페이지 확인 결과 'It works!'가 출력되었다면 서비스 상태가 정상적인 것
      curl -i 172.30.1.44


    6. service라는 폴더를 생성해 load-balancer-example.yaml 파일을 생성함
      - 일반적으로 기업에서는 서비스별로 폴더를 구분해서 관리함
      mkdir service
      cd service
      vi load-balancer-example.yaml


    7. load-balancer-example.yaml 파일 작성
      - hello-world라는 이름의 애플리케이션을 디플로이먼트 형태로 배포
      - 로드밸런서 테스트를 위해 파드를 5개 생성함
      apiVersion : apps/v1
      kind : Deployment
      metadata :
        labels :
          app.kujbernetes.io/name : load-balancer-example
        name : hello-world
      spec :
        replicas : 5		# 5개의 파드를 생성하도록 지정
        selector :
          matchLabels :
            app.kubernetes.io/name : load-balancer-example
        template :
          metadata :
            labels :
              app.kubernetes.io/name : load-balancer-example
          spec :
            containers :
            - image : gcr.io/google-samples/node-hello:1.0
              name : hello-world
              ports :
              - containerPort : 8080		# 컨테이너(파드)에서 8080 포트를 사용하도록 지정


    8. kubectl apply 명령어를 이용해 디플로이먼트를 생성 후 상태를 확인함
      - 5개의 파드가 Ready 상태임을 확인할 수 있음
      kubectl apply -f load-balancer-example.yaml



    9. hello-world 애플리케이션에 대한 디플로이먼트 상태를 확인함
      - kubectl describe 명령어를 사용하여 자세한 정보를 확인할 수 있음
      kubectl get deployments hello-world


    10. 실제로 파드가 5개 만들어졌는지 확인
      - kubectl describe 명령어를 사용해 자세한 정보 확인 가능
      kubectl get replicasets


    11. hello-world라는 디플로이먼트를 외부에 노출시키기 위한 서비스 오브젝트를 생성함
      kubectl expose deployment hello-world --type=LoadBalancer --name=exservice


    12. 포트를 확인하기 위해 서비스 오브젝트에 대한 정보를 얻음
      - 외부에서 접속할 때에는 32312 포트 사용
      kubectl get services exservice
       

    13. 파드 정보 확인
      - worker1에 5개의 파드가 생성된 것을 확인할 수 있음
      - 워커 노드가 2개 이상이라면 파드가 분배되어 생성됨
      kubectl get pods --output=wide


    14. curl을 명령어를 이용해서 로드밸런서 결과를 확인
      - 워커 노드의 IP를 사용해 접속함
      - 주소로 접속할 때 만들어진 5개의 파드 중 임의의 파드가 응답하게 됨
      - 5개의 파드 중 응답하는 파드가 임의로 선택되고 동일한 내용을 출력하도록 하는 것
      - 로드밸런서 서비스는 별도의 스위치 장비가 필요해서 비용이 많이 들어서 대안으로 사용할 수 있는 것

     

    인그레스

    - 클러스터 외부에서 내부로 접근하는 요청(HTTP, HTTPS 요청)을 어떻게 처리할지 정의해 둔 규칙들의 모음

    - 일반적인 로드밸런서와는 다르게 IP가 아니라 URL로 경로를 찾아줌

    - 어떤 URL 경로로 요청이 왔을 때 어떤 서비스로 연결하라는 '규칙'을 정의한 것이 전부

    - 실제 인그레스 규칙에 맞게 경로를 찾는 행위를 하는 것은 인그레스 컨트롤러(Ingress Controller)

     

    1. nginx 인그레스 컨트롤러(Nginx Ingress Controller)를 설치함
      - 깃허브에 설치하기 위한 파일 제공
      kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.12.2/deploy/static/provider/cloud/deploy.yaml


    2. ValidatingWebhookConfiguration을 활성 상태로 두면 인그레스 테스트 과정에서 오류가 발생하니 삭제함
      kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission


    3. 인그레스를 테스트하기 위한 서비스와 파드를 생성하여 내용을 작성함
      - cafe.yaml이라는 파일을 생성함
      - 들어오는 주소(URL)에 따라 서비스를 연결하도록 정의
      - tea-svc 서비스에는 tea 파드를 연결하고, coffee-svc 서비스에는 coffee 파드를 연결하도록 작성
      apiVersion : apps/v1
      kind : Deployment
      metadata :
        name : coffee		# coffee라는 디플로이먼트 생성
      spec :
        replicas : 2
        selector :
          matchLabels :
            app : coffee
        template :
          metadata:
            labels:
              app: coffee
          spec :
            containers :
            - name : coffee
              image : nginxdemos/nginx-hello:plain-text
              ports :
              - containerPort : 8080
      ---
      apiVersion : v1
      kind : Service
      metadata :
        name : coffee-svc		# coffee-svc라는 서비스 생성
      spec :
        ports :
        - port : 80
          targetPort : 8080
          protocol : TCP
          name : http
        selector :
          app : coffee
      ---
      apiVersion : apps/v1
      kind : Deployment
      metadata :
        name : tea		# tea라는 디플로이먼트 생성
      spec :
        replicas : 3
        selector :
          matchLabels :
            app : tea
        template :
          metadata:
            labels:
              app: tea
          spec :
            containers :
            - name : tea
              image : nginxdemos/nginx-hello:plain-text
              ports :
              - containerPort : 8080
      ---
      apiVersion : v1
      kind : Service
      metadata :
        name : tea-svc		# tea-svc라는 서비스 생성
      spec :
        ports :
        - port : 80
          targetPort : 8080
          protocol : TCP
          name : http
        selector :
          app : tea


    4. kubectl apply 명령어를 이용해 디플로이먼트와 서비스를 생성함
      kubectl apply -f cafe.yaml
    5. 생성된 디플로이먼트, 서비스, 파드의 상태를 확인함
      - 파드는 Running 상태여야 함
      - 디플로이먼트, 서비스, 파드가 생성된 것을 확인할 수 있음
      kubectl get deploy,svc,pods


    6. 요청 경로에 따라 서비스를 연결하기 위한 cafe-ingress.yaml 파일을 생성함
      - 엔드포인트가 /tea인 요청은 tea-svc 서비스에 연결하고 /coffee인 요청은 coffee-svc 서비스에 연결함
      apiVersion: networking.k8s.io/v1
      kind: Ingress
      metadata:
        name: cafe-ingress
      spec:
        ingressClassName: nginx  # 최신 방식
        rules:
        - http:
            paths:
            - path: /tea
              pathType: Prefix
              backend:
                service:
                  name: tea-svc
                  port:
                    number: 80
            - path: /coffee
              pathType: Prefix
              backend:
                service:
                  name: coffee-svc
                  port:
                    number: 80


    7. 인그레스를 생성함
      kubectl apply -f cafe-ingress.yaml


    8. 인그레스를 확인함
      - ADDRESS 필드에 IP가 설정되어 있어야 함
      - ADDRESS가 부여되는 동안 시간이 필요하므로 2~3분 후에 재실행시 ADDRESS가 보임
      kubectl get ingress


    9. 인그레스에 대한 정보(파드, 서비스)를 확인하기 위해 다음 명령어 실행
      - 명령어 실행 결과 ingress-nginx-controller라고 이름 붙여진 파드가 Running 상태이면 정상
      kubectl get pod -n ingress-nginx


    10. nginx 인그레스 컨트롤러의 포트 번호 확인
      kubectl get svc -n ingress-nginx


    11. 외부에서 인그레스의 ADDRESS 필드에 설정된 IP주소로 HTTP 요청을 전송해 인그레스가 올바르게 설정되었는지 확인함
      - 엔드포인트 /coffee에 대한 요청은 coffee-svc 서비스에 전달되어 coffee 파드가 응답하고, /tea에 대한 요청은 tea-svc 서비스에 전달되어 tea 파드가 응답함
      curl -i http://192.168.0.8:30130/coffee
      curl -i http://192.168.0.8:30130/coffee
      curl -i http://192.168.0.8:30130/tea

     

    02. 쿠버네티스 보안

    • 쿠버네티스 보안 권고 조치
      - 취약점이 있거나 구성이 잘못된 컨테이너 및 파드를 점검할 수 있어야 함
      - 최소한의 권한으로 컨테이너와 파드를 실행해야 함
      - 네트워크를 분리해 다른 시스템에 영향을 주지 않도록 해야함
      - 방화벽을 사용해 불필요한 네트워크 연결을 차단해야 함
      - 강력한 인증 및 권한 부여를 사용해 사용자 및 관리자 접근을 제한해야 함
      - 관리자가 모든 활동을 모니터링하면서 잠재적/악의적 활동에 대응할 수 있도록 주기적으로 로그 감사를 해야 함
      - 정기적으로 모든 쿠버네티스 설정을 검토하고 취약점 스캔 및 보안 패치가 적용되어 있는지 확인해야 함

    - 컨테이너 이미지의 안정성, 인증과 권한 및 보안 감사를 강조

    - 취약점 점검에는 하버(Harbor) 같은 컨테이너 이미지 레지스트리를 이용할 수 있음

     

    포스트맨 설치하기

    - 간단하고 편리하게 API를 테스트할 수 있는 도구

    - https://www.postman.com/downloads/에 접속한 후 Windows 64-bit 버튼을 클릭해 exe 파일을 내려받음

     

    - 내려받은 exe 파일을 더블클릭하고 설치를 진행함

    - 로그인 창이 뜨면 왼쪽 아래 Skip and go to the app을 클릭해 포스트맨 화면으로 넘어감

     

     

    RBAC

    - RBAC(Role-Based Access Control)은 역할을 기반으로 쿠버네티스에 접속할 수 있는 권한을 관리함

    - 사용자(user)와 역할(role) 두 가지를 조합해 사용자에게 권한을 부여할 수 있음

    ex) 특정 사용자에 대해 쿠버네티스 클러스터에는 접속할 수 없고 특정 파드에만 접속할 수 있게 권한을 설정할 수 있음

    - 쿠버네티스에서 RBAC 기반의 권한을 관리하는 것이 서비스 어카운트

    - 역할(role)에는 단순 역할과 클러스터 역할 두 가지 유형이 있음

    • 단순 역할 : 그 역할이 속한 네임스페이스에만 적용됨
    • 클러스터 역할 : 클러스터 전체에 적용

     

    kind : Role
    apiVersion : rbac.authorization.k8s.io/v1
    metadata :
      namespace : rbac
      name : role
    rules :				# 권한에 대한 규칙을 지정
    - apiGroups : [""]		# 역할이 적용될 그룹
      resources : ["pods"]		# 어떤 자원에 접근할지 명시
      verbs : ["get"]		# 어떤 동작이 가능한지 명시

     

     

    서비스 어카운트 개념 및 생성 방법

    - 서비스 어카운트(service account)는 클라이언트가 쿠버네티스 API를 호출할 때 사용함

    - 사람에게 부여하는 것이 아니라 쿠버네티스 API에 접근하는 시스템에 부여하는 권한

    - 서비스 어카운트는 쿠버네티스에 존재하는 오브젝트이며, 네임스페이스마다 정의할 수 있지만, 각 네임스페이스에는 기본적으로 'default 서비스 어카운트'가 자동으로 생성됨

    - 서비스 어카운트를 이해하려면 역할과 역할 바인딩이라는 개념을 먼저 이해해야 함

    구분 역할 역할 바인딩
    설명 어떤 리소스에 어떤 액션을 수행할 수 있는지를 정의함 주체(사용자 혹은 그룹)에 역할을 연결하는 것을 의미함

     

    1. 네임스페이스 생성
      - account라는 이름의 네임스페이스를 생성함
      kubectl create namespace account


    2. account 네임스페이스에 api-service-account라는 서비스 계정을 만듦
      - 생성한 서비스 계정으로 클러스터에 접속하려면 클러스터 역할(ClusterRole)을 정의해야 함
      - 클러스터 역할 : 서비스 계정이 클러스터의 어떤 리소스(노드, 파드, 볼륨 등)에 접속할 수 있을지를 정의하는 것
      kubectl create serviceaccount api-service-account -n account # -n account가 account라는 네임스페이스를 이용하겠다는 의미


    3. api-cluster-role.yaml라는 이름의 파일을 작성함
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRole
      metadata:
        name: api-cluster-role
        namespace : account # 앞에서 정의한 네임스페이스 지정
      rules:
        - apiGroups:  # 역할이 사용할 API 그룹들
            - ""
            - apps
            - autoscaling
            - batch
            - extensions
            - policy
            - rbac.authorization.k8s.io
          resources:  # 클러스터의 어떤 리소스(ex : 파드, 볼륨 등)에 접근 가능한지 지정
            - pods
            - componentstatuses
            - configmaps
            - daemonsets
            - deployments
            - events
            - endpoints
            - horizontalpodautoscalers
            - ingresses
            - jobs
            - limitranges
            - namespaces
            - nodes
            - persistentvolumes
            - persistentvolumeclaims
            - resourcequotas
            - replicasets
            - replicationcontrollers
            - serviceaccounts
            - services
          verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
          # 리소스에 접속해서 어떤 것들을 수행할 수 있을지에 대한 행위(verbs)를 지정


    4. 생성한 api-cluster-role.yaml 파일을 실행해 클러스터 역할을 생성함
      kubectl apply -f api-cluster-role.yaml


    5. 참고) 사용 가능한 리소스를 확인하는 명령어
      - 실습에선 서비스 어카운트 계정으로 클러스터의 모든 리소스에 접근할 수 있도록 지정했지만 권고 사항은 아님
      - 필요한 리소스에만 접근할 수 있도록 지정하는 것이 좋음
      kubectl api-resources


    6. 클러스터 역할과 서비스 어카운트를 매핑할 역할 바인딩을 생성함
      - api-cluster-role-binding.yaml라는 이름의 파일을 작성함
      - 역할 바인딩을 사용해 api-cluster-role을 api-service-account에 매핑(혹은 바인딩)함
      apiVersion: rbac.authorization.k8s.io/v1
      kind: ClusterRoleBinding
      metadata:
        name: api-cluster-role-binding
      subjects: # 주체는 서비스 어카운트
      - namespace: account
        kind: ServiceAccount
        name : api-service-account
      roleRef:
        apiGroup: rbac.authorization.k8s.io
        kind: ClusterRole
        name: api-cluster-role		# api-cluster-role을 api-service-account에 매핑


    7. kubectl apply 명령어를 이용해 역할 바인딩을 생성함
      kubectl apply -f api-cluster-role-binding.yaml


    8. 바인딩이 잘 되었는지 테스트함
      - account 네임스페이스의 api-service-account 계정으로 파드의 리스트를 보여 주는지를 검증함
      - api-service-account 계정으로 파드의 리스트를 보여 줄 수 있다는 yes 결과가 나옴
      kubectl auth can-i get pods --as=system:serviceaccount:account:api-service-account

      • 사용된 명령어의 옵션
        • kubectl auth can-i : 현재 사용자가 지정된 작업을 수행할 수 있는지를 알고자 할 때 사용함
        • --as=system:serviceaccount:account:api-service-account : account 네임스페이스에서 api-service-account 계정으로 파드 목록을 볼 수 있는지 확인
    9. API 호출을 이용해 테스트함
      - API 호출, HTTP 호출을 하려면 서비스 계정과 함께 서비스 계정과 연결된 토큰이 있어야 함
      - 쿠버네티스 1.24버전부터는 시크릿 이름을 자동으로 가져오지 않음
      • 시크릿을 생성함
        kubectl create secret generic api-service-account-token \
          --type=kubernetes.io/service-account-token \
          -n account \
          --from-literal=unused=unused \
          --dry-run=client -o yaml | \
          kubectl annotate -f - kubernetes.io/service-account.name=api-service-account --local -o yaml | \
          kubectl apply -f -


      • 패치로 Secret 연결
        kubectl patch serviceaccount api-service-account -n account \
          --type=json \
          -p='[{"op": "add", "path": "/secrets", "value": [{"name": "api-service-account-token"}]}]'


      • api-service-account 계정과 연결된 시크릿 이름 가져옴
        kubectl get serviceaccount api-service-account -o=jsonpath='{.secrets[0].name}' -n account

        • 명령어의 설명
          • kubectl get serviceaccount api-service-account :api-service-account라는 이름의 서비스 어카운트를 조회
          • -o=jsonpath='{.secrets[0].name}' : jsonpath는 필요한 정보만 추출할 때 사용하고 '{.secrets[0].name}'은 시크릿 이름만을 출력하겠다는 의미
          • -n account : account라는 네임스페이스를 사용함

    10. 시크릿 이름을 base64로 디코딩된 토큰을 가져옴
      - 이 토큰은 API를 호출할 때 토큰으로 사용됨
      kubectl get secrets api-service-account-token -o=jsonpath='{.data.token}' -n account | base64 --decode

      • 명령어의 설명
        • kubectl get secrets api-service-account-token : api-service-account-token이라는 시크릿 이름에서 사용하고 있는 시크릿을 조회함
        • o=jsonpath='{.data.token}' : 토큰 정보를 출력함
        • -n account : account라는 네임스페이스를 사용함
        • base64 --decode : 토큰을 디코딩된 문자열로 표현하되, 64자리 집합을 사용하는 6비트 숫자로 표현
    11. 외부에서 접근하기 위해 클러스터 엔드포인트 정보를 가져와야 함
      - 클러스터 엔드포인트(IP, DNS) 정보를 가져옴
      - 포트 정보만 사용할 예정
      kubectl get endpoints | grep kubernetes


    12. 포스트맨으로 접속함
      - API 호출을 테스트할 URL 정보와 토큰 정보를 이용함
      1. + 버튼을 클릭해 새로운 요청(request)창을 생성


      2. 톱니바퀴 모양 버튼을 누르고 settings를 누름


      3. 'SSL certificate verification' 항목을 OFF로 바꿔주고 창을 닫음


      4. 주소창에 https://[엔드포인트 주소]/api/v1/namespaces를 입력함
        - 앞에서 얻은 엔드포인트 주소는 클러스터 내부에서 사용하는 네트워크 주소
        - 외부에서 이 주소로 접속하면 연결되지 않으므로 앞에서 얻은 엔드포인트 주소 대신에 '마스터 노드의 IP 주소'를 입력함
        - 그다음 Authorization 메뉴를 클릭하고 'Type'에서 Bearer Token을 선택함
        - 이후 'Token' 부분에 앞에서 얻었던 base64로 디코딩된 토큰(혹은 직접 생성한 토큰)을 입력함
        - 1.24이상의 버전을 사용한다면 직접 생성한 토큰을 입력함


      5. Send 버튼을 클릭하면 다음과 같은 정보가 나옴
        - 출력 결과는 네임스페이스, UID, 리소스 버전 및 기타 많은 정보를 보여 주고 있음
        - API 호출에 대한 검증이 성공임을 의미함