Kubernetes/쿠버네티스 모범 사례 스터디

기본 서비스 설치

막이86 2023. 11. 14. 15:15
728x90

쿠버네티스 모범 사례을 요약한 내용입니다.

간단한 다중 계층 어플리케이션을 쿠버네티스에 설치해보기

1.1 애플리케이션 개요

  • 실습을 위해 사용할 애플리케이션은 레디스(Radis) 백엔드에 데이터를 저장하는 간단한 저널 서비스 입니다.
    • Nginx를 이용한 정적 파일 서버
    • 두 개의 웹 URL을 제공
    • SSL 인증 관리는 Let’s Encrypt(https://letsencrypt.org/) 사용
    • YAML 설정 파일을 이용
    • 핼름 차트를 사용

1.2 설정 파일 관리

쿠버네티스에 설정 관리 방법 알아보기

  • 쿠버네티스에서는 모든 것을 선언적으로 표현
    • 클러스터 내에서 애플리케이션의 의도한 상태 및 구성요소에 대해 상태를 정의
  • 클러스터의 상태를 보는 명령적 접근 방법도 있기는 하지만 선언적 방법을 선호
    • 클러스터가 어떻게 특정 상태가 되었는지 이해하고 그대로 복제하는 것이 매우 어려워짐
    • 문제가 발생했을 때 해결하는 것도 어려워짐
  • 쿠버네티스는 YAML과 JSON을 지원하지만 애플리케이션의 상태를 선언할 때는 YAML을 선호
  • YAML 파일에 선언된 상태는 진실의 원천
    • YAML 파일 변경 이력을 관리해야 함
    • 변경사항을 되돌릴수 있도록 관리 해야 함
    • 버전 관리 및 코드 리뷰를 이용한 모범 사례를 통해 애플리케이션의 선언적 상태를 관리 할 수 있음
  • 애플리케이션을 파일 시스템에 배치할 때, 파일 시스템의 디렉터리를 이용해 컴포넌트를 구성하는 것이 좋음
  • 일반적으로 팀에서 필요한 모든 애플리케이션 서비스 정의를 단일 디렉터리에 넣고 하위 디렉터리에 애플리케이션 구성 요소를 저장
    • 책의 예제에서는 아래와 같이 배치
    journal/
    	frontend/
    	redis/
    	fileserver/
    
  • 여러 지역이나 클러스터에 애플리케이션을 배포하기 시작하면 파일 배치는 점점 더 복잡해짐

1.3 디플로이먼트를 이용한 복제 서비스 생성

  • 프론트엔드부터 차근차근 애플리케이션을 작성해보기
  • 프론트엔드 애플리케이션은 타입스크립트로 구현한 Node.js 애플리케이션
  • 애플리케이션에서는 /api/* 경로에 대한 요청을 처리하는 HTTP 서비스가 존재하며 8080포트로 노출합니다.
  • 저널 항목 추가, 삭제, 반환에는 레디스 백엔드를 사용합니다.

1.3.1 이미지 관리 모범 사례

  • 이미지 구축 과정은 공급망 공격에 취약합니다.
    • 신뢰할 수 있는 소스의 의존 이미지에 악의적인 사용자가 코드나 바이너리를 삽입해서 애플리케이션에 이를 내장시키는 방법
  • 이미지 구축시 반드시 잘 알려지고 믿을 수 있는 이미지 공급자를 통해서 구축해야 합니다.
  • 밑바닥부터 이미지를 구축하는 방법도 있음
    • 상황에 따라 복잡 할 수 있음
  • 네이밍(naming)과 관련된 모범 사례
    • 이미지 레지스트리에 존재하는 컨테이너 이미지 버전을 변경할 수 있음
      • 이미지 버전 태그를 변경하지 않는 것이 좋음
    • 의미론적 버전과 이미지가 빌드된 커밋의 SHA 해시와 결합한 네이밍하는 것을 권장(v1.0.1-bfeda01f)
    • latest를 사용하면 새로운 이미지를 구추할 때마다 latest 이미지 자체가 변경되기 때문에 좋지 않음

1.3.2 애플리케이션 레플리카 생성

  • 프론트엔드 애플리케이션은 스테이트리스(stateless) 입니다.
    • 상태는 레디스 백엔드에서 전적으로 관리
    • 트래픽에 영향을 주지 않고 임의로 복제 가능
  • 예상치 못한 장애에 대응하거나 롤아웃(rollout)을 위해 재시작할 때 다운타임(downtime)이 생기지 않으려면 최소 두 개의 레플리카(reolica)를 실행하는 것이 좋습니다.
  • 쿠버네티스에서 레플리카셋(ReplicaSet)은 컨테이너화된 애플리케이션의 레플리카를 관리하는 리소스 입니다.
  • 레플리카셋을 사용하는대신 디플로이먼트(Deployment) 리소스를 사용하는 것을 권장
    • 레플리카셋의 복제 기술과 더불어 버전 관리, 단계적 롤아웃도 지원
  • 디플로이먼트를 사용하면 쿠버네티스에 내장된 도구를 이용해 앺ㄹ리케이션의 버전을 변경할 수 있음
  • 쿠버네티스 디플로이먼트 리소스는 다음과 같음
  • apiVersion: extensions/v1beta1 kind: Deployment metadata: labels: run: frontend name: frontend namespace: default spec: replicas: 2 selector: matchLabels: run: frontend template: metadata: labels: run: frontend spec: containers: - image: brendanburns/journal-server:latest imagePullPolicy: Always name: frontend terminationMessagePath: /dev/termination-log terminationMessagePolicy: File resources: request: cpu: "1.0" memory: "1G" limits: cpu: "1.0" memory: "1G"
  • 디플로이먼트에서 명심해야할 사항이 몇가지 있습니다.
    • 디플로이먼트, 레플리카셋, 디플로이먼트의 파드를 찾을 수 있도록 레이블을 사용해야함
      • 예제 코드에서 app: frontend 레이블을 추가 했으며 이를 통해 한 번의 요청으로 특정 계층의 모든 리소스를 조사할 수 있습니다.
  • YAML의 여러 곳에 주석을 작성하면 좋습니다.
    • 이 설정을 처음 접하는 사람이 이해하는데 도움을 줌
  • 컨테이너의 리소스 요청(requet) 과 제한(limit) 에도 주목해야 함
    • 요청은 애플리케이션을 실행하는 호스트 장비가 보장해주는 리소스 크기
    • 제한은 컨테이너가 사용할 수 있는 최대 리소스 크기
    • 요청과 제한을 동일한 값으로 설정한다면 대부분 예상대로 동작을 하지만 리소스 이용률을 높일수 없다는 단점이 있음
    • 과도한 스케줄링되거나 유휴 리소스를 과소비하는 것을 방지하는 장점은 있지만 세밀하게 튜닝하지 않으면 리소스를 최대로 사용할 수 없음
    • 리소스 모델을 더욱 잘 이해한다면 요청과 제한을 다르게 설정할 수 있음
  • 디플로이먼트 리소스를 버전 관리 시스템에 올리고 쿠버네티스에 배포
    git add frontend/deployment.yaml
    git commit - "Added deployment" frontend/deployment.yaml
    kubectl apply -f frontend/deplyment.yaml
    
  • frontend/deployment.yaml
  • 클러스터 항목과 소스 관련 항목이 정확히 일치해야 합니다.
    • GitOps로 지속적 통합CI과 지속적 배포CD를 자동화하여 특정 브랜치(branch)만 운영에 배포하는 것
    • 처음부터 완벽한 CI/CD 파이프라인을 만드는 것이 부담스럽게 느껴질수 있음
      • CI/CD가 주는 신뢰성 밎 자동화로 인해 설정하는 시간이 아깝지 않음
      • 이미 배포된 애플리케이션에 CI/CD를 끼워 넣는것은 굉장히 어려움

1.4 HTTP 트래픽을 처리하는 외부 인그레스 설정

  • 애플리케이션을 배포했지만 현재로선 아무도 접근할 수 없음
    • 클러스터 리소스는 기본적으로 오직 클러스터 내에서만 접근 가능
    • 외부 IP 주소를 할당해주는 서비스와 로드 밸런서를 생성하고 컨테이너로 트래픽을 보내야 함
  • 외부에 노출하려면 두개의 쿠버네티스 리소스를 사용
    • TCP 또는 UDP 트래픽을 로드 밸러싱하는 서비스를 사용
      • 책의 예제는 TCP 사용
    • 인그레스(ingress) 리소스는 HTTP 경로와 호스트 기반의 요청을 지능적으로 라우팅(routing) 할수 있는 HTTP(S) 로드 밸런싱을 지원
  • 인그레스를 배치하면 향후 서비스 확장 측면에서 유연성을 확보할 수 있음
  • 인그레스 리소스를 정의하기 전에 인그레스가 가리킬 쿠버네티스 서비스가 필요함
    • 서비스에서는 레이블을 사용해 이전에 생성한 파드를 가리키도록 작성
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: frontend
      name: frontend
      namespace: default
    spec:
      ports:
      - port: 8080
        protocal: TCP
        targetPort: 8080
      selector:
        app: frontend
      type: ClusterIP
    
  • 클러스터에서 인그레스가 동작하려면 서비스 리소스와 달리 인그레스 컨트롤러 컨테이너가 필요합니다.
    • 클라우드가 제공하는 것을 쓰거나 오픈 소스 서버로된 다양한 구현체를 선택할 수 있음
    • 오픈 소스 인그레스 공급자를 설치하기로 결정했다면 헬름 패키지 관지라를 이용해서 설치하고 관리하는 것이 좋음
    • 인그레스 공급자로는 nginx 또는 haproxy가 널리 사용되고 있음
    apiVersion: apps/v1
    kind: Ingress
    metadata:
      name: frontend-Ingress
    spec:
      rules:
      - http:
          paths:
          - path: /api
            backend:
              serviceName: frontend
              servicePort: 8080
    

1.5 컨피그맵으로 애플리케이션 설정

  • 애플리케이션을 개발할 때 설정을 분리하는 것이 좋음
    • 동일한 바이너리에 환경에 따라 달라지는 설정을 하고 싶을 때
      • 바이너리를 수정한다면 민첩성이 떨어짐(빌드, 배포를 해야하기 때문에)
    • 설정을 통해서 요구사항 변경이나 기능 활성화 및 비활성화를 빠르게 할 수 있음
  • 쿠버네티스에서는 컨피그맵 리소스로 설정을 정의
    • 컨피그맵은 설정 정보나 파일을 나타내는 다중 키/값 쌍을 가지고 있음
    • 설정 정보를 파드 내의 컨테이너에 파일이나 환경변수 형태로 전달
  • 컨피그맵 정의
  • kubectl create configmap frontend-config --from-literal=journalEntries=10
  • 환경 변수를 이용해 애플리케이션에 설정 정보를 적용
  • frontend/deployment.yaml 컨피그맵
  • 설정 변경만으로 이미 존재하는 파드가 실제로 갱신되지 않음
    • 파드를 재시작해야 설정이 반영 됨
  • 컨피그맵의 이름에 버전 숫자를 넣어 관리하는 것이 좋음
    • frontend-config → frontend-config-v1 → frontend-config-v2 → ....
    • 디플로이먼트가 적절한 헬스 체크를 통해서 자동으로 멈췄다가 재시작 됨
    • 롤백의 경우에도 v1이 있기 때문에 롤백이 쉬움

1.6 시크릿 인증 관리

  • 어떤 어플리케이션이든지 서비스 간 보안 연결을 필요로 함
  • 완벽하진 않지만 사용자와 데이터 보안을 보장할 수 있음
  • 개발과 운영 데이터베이스를 연결하는 실수를 방지할 수 있음
  • 소스코드 및 이미지 안에 비밀번호를 저장하면 편리하기는 하지만 보안에 좋지 않음
    • 소스코드의 경우 소스코드를 보는 모든 사용자가 볼수 있음(운영 DB의 경우 개발자 모두가 비밀번호를 볼 필요는 없음)
    • 이미지에 저장하는 경우 배포환경에 따라 이미지를 생성해야함
      • 이미지가 외부 유출되는 경우 좋지 않아 보임
  • 컨피그맵처럼 비밀번호를 저장해 애플리케이션에 특화된 설정으로 전달 하는 것이 좋음
  • 시크릿 리소스 생성 방법
  • kubectl create secret generic redis-passwd --from-literal=passwd=${RANDOM}

기본적으로 시크릿은 암호화되지 않은 상태로 쿠버네티스 안에 저장됩니다. 시크릿을 암호화해서 저장하려면 쿠버네티스와 키 공급자를 연동하여 클러스터 내의 모든 시크릿을 암호화할 수 있는 키를 받아야 합니다. etcd 데이터베이스에 대한 공격을 막을 수는 있습니다. 그러나 API 서버를 통해 접근하는 것에 대해서는 적절한 보안이 필요합니다.

<aside> ❓ 시크릿에 대한 자세한 사용방법을 알아봐야할듯....

</aside>

  • 시크릿을 저장한 다음 배포 시점에 실행 중인 애플리케이션과 시크릿을 결합 해야 함
    • 장비가 물리적인 피해를 입더라도 공격자가 시크릿을 취득하기 어려움(상황에 따라 차이는 있을 수 있음)
  • 디플로이먼트에 시크릿 볼륨을 추가 하려면 YAML에 명시해야 함
  • frontend/deployment.yaml 시크릿 추가

1.7 간단한 스테이트풀 데이터베이스 배포

  • 스테이트풀 배포는 스테이트리스와 개념은 비슷하지만 상태 때문에 좀더 복잡함
  • 노드 상태, 업그레이드, 리밸런싱 등의 이유로 파드가 다시 스케줄링이 될때 스테이트풀 데이터가 손상될 수 있음
  • 스케줄링으로 인한 데이터 손상을 방지하기 위해 원격 퍼시스턴트볼륨(PersistentVolume)을 사용해야 함
  • 퍼시스턴트볼륨은 일반적으로 원격 스토리지에 존재하게 됨
    • 파일 기반의 네트워크 파일 시스템, 서버 메시지 블록, 블록 기반의 iSCSI, 클라우드 기반 디스크 등 다양
  • 데이터베이스와 같은 애플리케이션의 경우 성능이 더 좋은 블록 기반 디스크를 선호
    • 성능이 주요 고려사항이 아니라면 유연성이 높은 파일 기반 디스크가 좋음
  • 레디스 서비스는 스테이트풀 리소스를 사용해 배포
  • 레디스는 퍼시스턴트볼륨을 얻기 위해 퍼시스턴트볼륨클레임을 사용
    • 레디스가 50GB 필요하다고 추상적으로 선언하면 쿠버네티스 클러스터는 적절한 퍼시스턴트볼륨을 제공할 방법을 결정</aside>
    • <aside> ❓ 클러스터에서??? 클러스터에 할당된 볼륨에서??
  • 디스크 명세가 다를 수 있는 여러 클라우드나 온프레미스 사이에서 이식할 수 있도록 스테이트풀셋을 작성 가능
  • 퍼시스턴트볼륨 타입은 오직 하나의 파드에 마운트 될수 있음
    • 퍼시스턴트볼륨은 파드 자신만이 할당 받음
  • 볼륨클레임을 사용하면 복제가 가능
  • 레디스 서비스의 단일 인스턴스를 배포하기 위한 yaml
  • readis.yaml
  • 읽기 스케일아웃이나 장애 탄력성을 위해 레디스 클러스터를 복제 해보기
    • 레플리카 수 : 3
    • 두 개의 새로운 레플리카를 레디스의 쓰기 마스터에 연결
  • 스테이트풀셋을 위한 헤드리스headless 서비스를 생성할 때 도메인 네임 시스템(DNS) 항목인 redis-0.redis도 생성 됨
    • 첫 번째 레플리카의 IP 주소
    • 첫 번째 레플리카를 구분하는 스크립트
    PASSWORD=$(cat /etc/redis-passwd/passwd)
    
    if [[ "${HSTNAME}" == "redis-0" ]]; then
    	redis-server --requirepass ${PASSWORD}
    else 
    	redis-server --slaveof redis-0.redis 6379 --masterauth ${PASSWORD} --requirepass
    fi 
    
    • 스크립트를 컨피크맵으로 생성
    kubectl create configmap redis-config --from-file=launch.sh=launch.sh
    
  • 완전한 세 개의 레디스 레플리카
  • redis-replica.yaml

1.8 서비스를 이용한 TCP 로드 밸런서 생성

  • 프론트엔드에서 레디스 서비스에 접근하기 위해서는 두개의 서비스를 생성 해야함
    • 레디스에서 데이터를 읽는 서비스 생성
    • apiVersion: v1 kind: Service metadata: labels: app: redis name: redis namespace: default spec: ports: - port: 6379 protocol: TCP targetPort: 6379 selector: app: default sessionAffinity: None type: ClusterIP
    • 쓰기는 레디스 마스터에서 직접 처리하도록 헤드리스 서비스를 생성
      • 헤드리스 서비스는 클러스터 IP주소를 가지지 않음
      • 스테이트풀셋 안의 모든 파드에 대한 DNS를 설정
        • redis-0.redis-write라는 DNS 이름으로 레디스 마스터에 접근 할 수 있음
        • apiVersion: v1 kind: Service metadata: labels: app: redis-write name: redis-write spec: clusterIP: None ports: - port: 6379 selector: app: redis

1.9 인그레스를 이용해 트래픽을 정적 파일 서버로 전달

  • 정적 파일 서버의 경우 HTML, CSS, 자바스크립트, 이미지 파일을 제공
  • API 서비스와 분리되어 있으므로 정적 파일 서버에 효과적으로 집중 할 수 있음
  • 인그레스 리소스를 사용하여 소규모의 마이크로서비스 아키텍처를 쉽게 만들 수 있음
  • 디플로이먼트 리소스를 사용해 NGINX 서버를 작성
  • apiVersion: apps/v1 kind: Deployment metadata: labels: app: fileserver name: fileserver namespace: default spec: replicas: 2 selector: matchLabels: app: fileserver template: metadata: labels: app: fileserver spec: containers: - image: my-repo/statif-files:v1-abcde imagePullPolicy: Always name: fileserver terminationMessagePath: /dev/termination-log terminationMessagePolicy: File resources: request: cpu: "1.0" memory: "1G" limits: cpu: "1.0" memory: "1G" dnsPolicy: ClusterFirst restartPolicy: Always
  • 로드 밸런서 추가
  • apiVersion: v1 kind: Service metadata: labels: app: frontend name: frontend namespace: default spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: frontend sessionAffinity: None type: ClusterIP
  • 인그레스 리소스에 새로운 경로를 추가
    • / 경로를 /api 경로 이후에 두는 것을 명심해야 함
    apiVersion: apps/v1
    kind: Ingress
    metadata:
      name: frontend-Ingress
    spec:
      rules:
      - http:
          paths:
          - path: /api
            backend:
              serviceName: frontend
              servicePort: 8080
    			- path: /
    				backend:
    					serviceName: nginx
    					servicePort: 80
    

1.10 헬름을 이용한 애플리케이션 파라미터화

  • 서비스를 다양한 환경에 배포하는 상황이 많음
    • 개발과 운영 환경의 설정을 따로 가지고 있어야함
    • 통합 테스트와 CI/CD를 고려했을 때 최소 다른 환경 세 곳에 배포를 해야 함
  • 클러스터 하나에서 다른 클러스터로 파일을 복사하는 실수를 함
    • ex) frontend/ 디렉터리 대신에 frontend-production/과 frontend-development/ 디렉터리 두개를 사용 시 파일이 동기화 되기 어려울 수 있음
      • 동기화에 대한 보장할 수 없음
  • 브랜치와 버전 관리를 사용할 수 있음
    • 운영과 개발을 브랜치로 관리하는 방법이 있음 (괜찮은 방법임)
    • 다양한 환경에 배포하게 될 경우 브랜치간의 매커니즘이 난해함
  • 여러가지 이유로 템플릿 시스템을 선택하게 됨
    • 설정의 뼈대인 템플릿과 특정 환경 설정으로 템플릿을 특수하게 만들어주는 파라미터와 결합
    • 쿠버네티스에는 다양한 템플릿 시스템이 있지만 가장 대중적인 시스템은 헬름 이다.
  • 헬름에서 애플리케이션은 차트Chart라는 파일 집합으로 시작함
    • 메타 데이터를 정의한 chart.yaml로 시작
    • 차트 디렉터리의 최상위에 위치함
    apiVersion: v1
    appVersion: "1.0"
    description: A Helm Chart for our frontend journal server.
    name: frontend
    version: 0.1.0
    
  • 템플릿 파일에서 frontent-deployment.yaml에서 아래와 같이 사용 가능
...
spec:
	relicas: 2
...
...
spec:
	replicas: {{ .replicaCount }}
...
  • values.yaml 파일에서는 replicaCount를 정의
  • replicaCount: 2
  • helm 도구를 이용하여 차트를 배포할 수 있음
  • helm install path/to/chart --values path/to/envirnment/values.yaml

1.11 서비스 배포 모범 사례

  • 쿠버네티스는 복잡하지만 강력한 시스템
    • 대부분의 서비스는 디플로이먼트 리소스로 배포되어야 함
      • 디플로이먼트는 중복과 확장을 위해 레플리카를 생성
    • 디플로이먼트는 로드 밸런서인 서비스를 통해 노출
      • 서비스는 클러스터 내부 혹은 외부에 노출 가능
      • HTTP로 노출하려면 인그레스 컨트롤러를 사용해야 함
      • 요렁 라우팅과 SSL도 추가 가능
    • 설정을 재사용하기 위해서는 파라미터화 해야함
      • 헬름과 같은 패키징 도구는 파라미터화를 위한 최고의 선택
728x90

'Kubernetes > 쿠버네티스 모범 사례 스터디' 카테고리의 다른 글

버전, 릴리스, 롤아웃  (1) 2023.11.14
지속적 통합, 테스트, 배포  (1) 2023.11.14
설정, 시크릿, RBAC  (1) 2023.11.14
모니터링과 로깅  (0) 2023.11.14
개발자 워크플로  (0) 2023.11.14