서론
이 글은 쿠버네티스로 구성되어있는 사내 서비스 클러스터를 활용하던 중 Ingress 및 여러 설정 등에 대한 이해도 부족으로 업무가 지연 된 경험이 있기에 그 부분을 바로잡고자 공부하기위해 적습니다.
쿠버네티스란?
클러스터 환경에서 컨테이너화 된 애플리케이션을 자동으로 배포하고 확장 및 관리하는데에 필요한 요소를 자동화하는 오픈소스 플랫폼입니다. 여러 서버로 구성 된 클러스터 환경을 구축하는 것에 쿠버네티스를 사용하면 로드밸런싱, 네트워크, 스토리지, 모니터링 등 시스템 운영에 필수적인 여러 컴포넌트를 쉽게 구축 할 수 있습니다.
쿠버네티스는 일반적으로 yaml 파일과 같은 형식의 파일로 구성합니다. 이렇듯 모든 작업을 소스코드로 관리하면 운영 시스템에 적용하기 전 검토에 유용하고, 사전 검증도 편해집니다.
또한, 쿠버네티스는 ‘의도된 상태’를 기준으로 관리합니다. 최초 의도한 상태와 현재 진행중인 상태를 쿠버네티스 컨트롤러가 자동으로 끊임없이 확인( go 언어의 watch )하며 차이점이 발생하면 현재 상태를 자동으로 의도 된 상태로 변경합니다. 예를들면 Nginx 서버가 갑자기 예상치 않게 종료되면 쿠버네티스는 곧바로 새로운 pod를 생성합니다.
클러스터의 전체 상태를 지속적으로 체크하여 자원의 할당이나 물리적인 리소스 등을 조절하고 활용합니다.
쿠버네티스의 주요 오브젝트
쿠버네티스의 주요 오브젝트로는 Pod, Deployment, Namespace 등이 있습니다. 이런 오브젝트등은 쿠버네티스 API 서버로 생성하는 영속성을 가지는 모든 실체로 애플리케이션을 실행하고 애플리케이션에 필요한 추가 리소스를 지정하고 고가용성 관련 설정 등 모든 쿠버네티스 작업은 다양한 오브젝트와 그 옵션의 조합으로 실행합니다.
Pod란?
Pod는 쿠버네티스 환경에서 컨테이너 애플리케이션을 실행하는 기본 단위입니다. Pod는 다른 Pod와 구분되는 고유한 네트워크와 스토리지를 가지고 확장 시 Pod 단위로 증성합니다. Pod는 각자 고유의 IP와 볼륨을 사용합니다. 별도의 설정이 없는 한 호스트 노드의 /var/lib/containers/{pod이름}의 경로에 디렉토리를 만들고 해당 공간을 마운트합니다.
Pod의 주요 옵션
resource
- cpu ( request, limit )
- 해당 Pod에 할당 할 CPU 리소스를 지정 할 수 있습니다.
- request : 적어도 이 정도의 리소스가 있어야 Pod를 생성합니다.
- limit : 이 리소스 량을 넘어서면 Pod가 죽습니다. 최대 사용 가능량입니다.
- 해당 Pod에 할당 할 CPU 리소스를 지정 할 수 있습니다.
- Memory ( request, limit )
- 해당 Pod에 할당 할 Memory 리소스를 지정 할 수 있습니다.
- request : 적어도 이 정도의 리소스가 있어야 Pod를 생성합니다.
- limit : 이 리소스 량을 넘어서면 Pod가 죽습니다. 최대 사용 가능량입니다.
- 해당 Pod에 할당 할 Memory 리소스를 지정 할 수 있습니다.
anti-affinity(affinity)
affinity는 pod가 어떤 노드에 생성 될 것 인지에 대해서 설정 할 수 있는 설정 값 입니다. 이러한 역할을 하는 옵션은 가장 대중적으로 NodeSelector가 있고 실제로 간단한 노드 지정은 이 옵션을 사용해도 문제가 없습니다. 하지만 affinity는 더 유연한 노드 선택 기능을 제공합니다.
- 특정 노드에는 Pod가 생성되지 않도록 ( Anti-affinity )
- 혹은 특정 노드에는 가급적 Pod가 생성되지 않도록 ( + prefered )
- 특정 노드에만 Pod가 생성 될 수 있도록 ( affinity )
- 혹은 가급적 특정 노드에 Pod가 생성 될 수 있도록 ( + prefered )
- 더 다양한 플러그인 제공 ( addedAffinity )
Deployment란?
Deployment는 Pod가 어떻게 배포 될 것인지에 대한 방법을 정의하는 오브젝트입니다. Pod의 갯수, 이미지 종류, 배포 방법등을 한번에 정리하여 원하는 형식으로 Pod가 배포 될 수 있도록 도와줍니다.
Deployment에서는 replica를 지정 할 수 있는데, 이 경우 기존의 Pod를 제거(delete)하더라도 그 숫자를 유지하기 위해 저절로 새로운 Pod를 생성합니다.
즉 Pod에 설정 할 리소스를 Deployment에 적어놓고 많은 Pod를 쉽게 배포 할 수 있습니다.
Namespace란?
Namespace는 클러스터를 구분하는 가상 클러스터의 단위입니다. 같은 Namespace에서는 같은 이름의 오브젝트를 만들지 못합니다. 이러한 Namespace은 애플리케이션을 구분하는 단위로 사용합니다. 예를들면 웹서버, 데이터베이스, 레디스 등의 애플리케이션 단위로 나눌 수 있습니다. 그리고 RBAC과 같은 제어 설정을 통해 Namespace 단위로 각 개발자가 접근 할 수 있는 영역을 나눌 수 있습니다. 뿐만 아니라 자원의 할당도 구분 할 수 있습니다.
하지만 Namespace는 결국 클러스터를 가상으로 구분하는 방식이기에 네트워크가 구분되어 있지 않습니다. 즉, 임의의 Namespace에서 다른 Namespace로 네트워크 통신은 가능합니다.
Service란?
Service는 Pod간 네트워크 연결을 담당하는 리소스입니다. 이 부분이 제가 어려움을 겪은 지점에 대한 해결책이 될 수 있을 것 같습니다.
Service가 필요한 이유은 Kubernetes는 기본적으로 확장 혹은 장애 발생시 유동적으로 Pod를 생성 및 제거하여 서비스의 안정도를 높이는 작업을 합니다. 이 과정에서 각 Pod가 할당받는 내부 IP 주소는 계속해서 변하게 되며, 관련 환경변수 등을 수동으로 관리하면 매우 어려운 작업이 됩니다. 여기서 Service는 각 Pod간의 연결을 유동적으로 관리해줍니다.
기본적으로 다음과 같은 특징을 가집니다.
- 다이나믹하게 종료되고 생성되는 파드를 자동으로 발견합니다.
- 변경이 잦은 IP 주소가 아닌 지속 가능한 도메인 레코드를 기반으로 쿠버네티스 서비스 이름을 이용해 통신합니다.
- 노드 내 실행 중인 여러 Pod로 부하를 분산하는 로드밸런싱 기능을 자체적으로 지원합니다.
Service 문서
- https://kubernetes.io/ko/docs/concepts/services-networking/service/
위 문서 중 특정 pod의 네트워크를 외부에 노출하기 위해 사용 할 만한 정보도 있는 것 같습니다.
- https://kubernetes.io/ko/docs/concepts/services-networking/service/#type-nodeport
Service Discovery란?
Service Discovery는 Service가 어떻게 새로운 Pod를 발견하는지에 대한 내용입니다. Service의 특성 상 같은 클러스터의 경우 상호간의 통신이 가능해야하는데, 새로운 Pod는 언제든 생성 될 수 있습니다. 그 때 어떻게 새로운 Pod가 생성됨을 알아내고 해당 Pod로 연결 될 수 있는 endpoint를 수집하는지에 대한 부분이 포함되어 있습니다.
기존의 Pod를 삭제하고 새로운 Pod를 띄우면 새로운 Pod는 기존의 Pod와 다른 내부 IP를 가집니다. 해당 Pod로의 접근은 큰 문제가 없는데, 이유는 서비스의 이름으로 접근이 가능하기 때문입니다.
그리고 동시에 kubernetes API 서버의 etcd 데이터베이스의 변경 내역을 실시간으로 반영해서 endpoints를 수정합니다.
여기서 로드밸런싱을 통해 여러개의 복제를 만들어내더라도 endpoints 관점에서 IP를 늘리는 것만으로 관리가 가능합니다.
CoreDNS란?
CoreDNS는 컨테이너 오케스트레이션 시스템 및 클라우드 환경에서 네트워크 서비스 디스커버리 및 DNS(Domain Name System) 서버로 사용되는 오픈 소스입니다. 일반적으로 클러스터의 Deployment로 2개의 Pod로 관리됩니다.
kubernetes 환경에서 CoreDNS는 다음의 동작을 위해 사용됩니다.
- 클러스터 내 DNS
- 보안 기능
- 네트워크 확장 용이 등
기본적으로 kubernetes에서 CoreDNS는 kube-system namespace에 Service가 존재하고 Configmap으로 관리됩니다.
CoreDNS가 다른 DNS와 다르게 동작하는 것은 아닙니다. 다른 DNS와 같이 클러스터 내부의 DNS역할을 수행하고 마찬가지로 IP가 없으면 캐싱합니다. 외부의 DNS도 마찬가지로 DNS의 역할을 수행합니다. 이 경우 외부 DNS 검색 관련 도메인도 추가해서 검색 DNS도 고정 할 수 있습니다.
LocalDNS란?
LocalDNS는 쿠버네티스 1.18버전에서 정식으로 도입되어 DNS 성능 향상에 도움을 줍니다. 일반적으로 CoreDNS의 캐싱 역할을 수행합니다.
클러스터 외부에서 내부의 Pod 연결 ( nodeport와 ingress를 AWS 기준에서 lb를 이용하여 구축 )
https://three-beans.tistory.com/entry/AWSEKS-%EC%BD%98%EC%86%94%EB%A1%9C-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-EKS-%E2%91%A3-ingress-AWS-LoadBalancer-Controller-%EA%B5%AC%EC%84%B1
Ingress란?
REST API의 느낌으로 경로마다 다른 Service로 전달되도록 할 수 있는 라우터 느낌의 리소스입니다.
Ingress 문서
- https://kubernetes.io/ko/docs/concepts/services-networking/ingress/
다른 추가적인 내용보다 좋은 예시와 설명이 있어서 공식 문서만 적겠습니다. 이번에 해결하지 못한 문제로 외부 망과 내부 망의 통신에 관련 된 내용이 있었는데, 이 부분에서 별도의 Ingress 경로에 VPC에서 내부와 Internet Gateway로 통할 수 있는 VPC로 설정하여 통신을 시도하면 가능하지 않을까 생각중입니다.
언젠가 해결해야 할 문제라 일단 적어놓겠습니다.
예상하기로는 Service의 nodeport를 별도로 설정 한 pod를 띄우고 해당 pod의 HTTP 포트 접근으로 Ingress 를 만들어 노출 및 라우팅하면 원하는 서비스를 만들 수 있지 않을까 생각됩니다.
ConfigMap란?
ConfigMap은 개별 애플리케이션의 환경변수를 설정합니다. 필수적으로 사용해야하는 오브젝트는 아니지만, Pod나 다른 오브젝트에서 불러와서 사용 할 수 있는 범용적인 환경변수 등을 지정해놓고 쉽게 사용 할 수 있습니다.
쿠버네티스의 Storage
일반적으로 로컬 환경에서 쿠버네티스 환경을 구축 한 후 파일을 저장하면 해당 파일은 영구적으로 보관되지 않습니다. 이 부분은 컨테이너 개념에서 쉽게 알 수 있는데, 일반적으로 도커 이미지를 통해 특정 컨테이너를 띄우면 해당 컨테이너에서 사용되는 디스크 공간을 로컬 내 디스크 공간에 파티션을 나누어 사용하는 것을 알고 있습니다.
이처럼 Pod 또한 삭제되는 과정에서 해당 Pod에 할당 된 공간에 저장 된 파일을 모두 삭제하게 됩니다.
이러한 문제를 해결하기 위해 영구적인 저장 공간이 필요하고 Persistence Volumn(PV)가 이러한 역할을 합니다.
Storage 개념에서 알아야 할 지식은 다음 3가지가 있습니다.
PV ( Persistence Volume )
PV는 실제로 저장되는 공간을 말합니다. 일반적으로 사용하는 하드디스크처럼 공간이 정해져 있으며, 해당 Pod에서 특정 디렉토리 구조로 접근해서 읽고 쓸 수 있는 실질적인 공간입니다.
PVC ( Persistence Volume Claim )
PVC는 PV를 만들어달라고 요청하는 리소스입니다.
우리는 어떠한 용도에 따라 적은 공간, 혹은 많은 공간을 사용해야 하는 경우가 있습니다. 메시지 브로커나 많은 양의 데이터를 처리하는 과정에서 중간 데이터 및 히스토리 성 데이터가 쌓이는 공간이 필요할 수 있습니다.
이러한 데이터를 S3와 같은 외부 스토리지에 의존 할 수 있지만, 아무래도 자주 읽는 경우 네트워크를 타면 큰 부하가 발생 할 수 있을텐데 그러면 해당 Pod의 스토리지를 매우 크게 잡을 수 있습니다. 예를들면 4TB의 공간을 할당하는 서버가 3대 필요하고 기껏해야 10GB의 공간 이상 사용하지 않는 서버가 10대 필요하다면, 두 PV의 속성은 달라야 합니다.
이 경우 PVC를 별도로 나누어서 하나의 PVC는 넉넉한 용량 및 설정 값을, 하나는 적정 용량과 설정 값을 포함하게 만들고 각 Pod의 Deployment 단계에서 별도의 PVC를 참조하게하면 PV를 쉽게 구분하여 사용 할 수 있습니다.
SC ( Storage Class )
SC는 스토리지에 대한 자체 성격을 정의하는 리소스입니다.
AWS에서는 EBS의 성격을 나눕니다. 예를 들면 GP2(현재는 안쓰이지만) GP3 과 같은 개별 스토리지 클래스가 있습니다. 이러한 일반 성향을 가진 스토리지 뿐 아니라 네트워크 통신에 유리한 형식의 스토리지 처럼 용도에 따라 다양한 스토리지 클래스가 있습니다.
이렇듯 스토리지 자체에 대한 환경을 정의해놓는 리소스가 SC입니다. 일반적으로 PVC에서 고정하거나, default SC를 설정해놓으면 자동으로 Pod가 떠오를 때 해당 SC를 사용하기도 합니다.
파일 스토리지
이 이야기를 빼먹을 수 없을 것 같아서 한 가지 더 적고자 합니다.
일반적으로 쿠버네티스의 스토리지는 한 Pod가 하나의 PV를 가집니다. PV는 모든 Pod가 분리되어 있기 때문에 특정 Pod에서 파일을 쓸 때 다른 Pod에 공유하려면 네트워크를 통해 전송하거나, API 및 훅 등의 방식으로 요청하는 등의 방식을 사용해야합니다.
파일 스토리지(AWS에서는 EFS)는 이러한 문제를 해결 할 수 있도록 도와줍니다. 하나의 파일 스토리지는 여러 Pod에서 동시에 참조 할 수 있으며, 특정 Pod가 파일 스토리지에 파일을 쓰면 같은 스토리지는 참조하는 Pod도 해당 파일을 읽을 수 있습니다.
물론 이 과정에서는 스토리지 서버가 분리되어 있기 때문에 로컬 스토리지인 PV보다는 느릴 수 있습니다만, 이 파일 스토리지의 네트워크를 최적화하는 다양한 방법도 있으며, 많은 클라우드 서비스에서 제공하고 있기 때문에 충분히 좋은 방법이 될 수 있습니다.
참고 링크
- https://kubernetes.io/ko/docs/concepts/services-networking/ingress/
- https://velog.io/@yange/kubernetes-coredns
- https://three-beans.tistory.com/entry/AWSEKS-%EC%BD%98%EC%86%94%EB%A1%9C-%EC%83%9D%EC%84%B1%ED%95%98%EB%8A%94-EKS-%E2%91%A3-ingress-AWS-LoadBalancer-Controller-%EA%B5%AC%EC%84%B1
- https://kubernetes.io/ko/docs/concepts/services-networking/service/#type-nodeport
- https://kubernetes.io/ko/docs/concepts/services-networking/service/