쿠버네티스 소개
쿠버네티스는 컨테이너화된 애플리케이션을 쉽게 배포하고 관리할 수 있게 해주는 소프트웨어 시스템입니다.
쿠버네티스는 클러스터의 운영체제로 생각할 수 있습니다. 쿠버네티스는 특정 인프라 관련 서비스를 제공하는데, 서비스 디스커버리, 스케일링, 로드밸런싱, 자가 치유, 리더 선출 등이 포함됩니다.
- 마스터 노드는 전체 쿠버네티스 시스템을 제어하고 관리하는 쿠버네티스 컨트롤 플레인을 실행합니다.
- 워커 노드는 실제 배포되는 컨테이너 애플리케이션을 실행합니다.
컨트롤 플레인(마스터)
- 쿠버네티스 API 서버는 사용자, 컨트롤 플레인 구성 요소와 통신합니다.
- 스케줄러는 애플리케이션의 배포를 담당합니다(애플리케이션의 배포 가능한 각 구성 요소를 워크 노드에 할당)
- 컨트롤러 매니저는 구성 요소 복제본, 워커 노드 추적, 노드 장애 처리 등과 같은 클러스터단의 기능을 수행합니다.
- etcd는 클러스터 구성을 지속적으로 저장하는 신뢰할 수 있는 분산 데이터 저장소입니다.
노드
워커 노드는 컨테이너화된 애플리케이션을 실행하는 시스템입니다.
- 컨테이너를 실행하는 도커, rkt 또는 다른 컨테이너 런타임
- API 서버와 통신하고 노드의 컨테이너를 관리하는 Kubelet
- 애플리케이션 구성 요소 간에 네트워크 트래픽을 로드밸런싱하는 쿠버네티스 서비스 프록시(kube-proxy)
쿠버네티스는 실행 중인 노드가 정지됐거나 다른 컨테이너를 위한 공간을 만들려고 노드에서 제거할 때 컨테이너를 클러스터 안에서 이동시킬 수 있습니다. 쿠버네티스에게 동일한 서비스를 제공하는 컨테이너를 알려주면 쿠버네티스는 하나의 고정 IP 주소로 모든 컨테이너를 노출하고 해당 주소를 클러스터에서 실행 중인 모든 애플리케이션에 노출합니다. 환경변수로 제공되지만 DNS로 서비스 IP를 조회할 수 있습니다. kube-proxy는 서비스를 제공하는 모든 컨테이너에서 서비스 연결이 로드밸런싱되도록 합니다. 서비스의 IP주소는 일정하게 유지되므로 클라이언트는 컨테이너가 클러스터 내에서 이동하더라도 컨테이너에 항상 연결할 수 있습니다.
빌드 프로세스는 도커 클라이언트가 수행하지 않습니다. 디렉토리의 전체 콘텐츠가 도커 데몬에 업로드되고 그곳에서 이미지가 빌드됩니다. 도커 클라이언트와 데몬은 같은 머신에 있을 필요는 없습니다.
서비스
클러스터 외부에서 파드에 접근케 하기 위해 쿠버네티스에게 레플리케이션컨트롤러에 의해 관리되는 모든 파드를 단일 서비스로 노출하도록 명령합니다.
파드는 일시적입니다. 실행 중인 노드가 실패할 수도 있고 삭제될 수 있고, 비정상 노드에서 파드가 제거될 수 있습니다. 이러한 상황에서는 레플리케이션 컨트롤러에 의해 생성된 파드로 대체됩니다. 새로운 파드는 다른 IP 주소를 할당받습니다.
항상 변경되는 파드의 IP 주소 문제와 여러 개의 파드를 단일 IP와 포트의 쌍으로 노출시키는 문제를 해결합니다. 서비스가 생성되면 정적 IP를 할당받고 서비스가 존속하는 동안 변경되지 않습니다. 파드에 직접 연결하는 대신 클라이언트는 서비스의 IP 주소를 통해 연결해야 합니다. 서비스의 IP와 포트로 유입된 요청은 그 순간 서비스에 속해 있는 파드 중 하나에게 전달됩니다.
파드
여러 프로세스를 단일 컨테이너로 묶지 않기 때문에, 컨테이너를 함께 묶고 하나의 단위로 관리할 수 있는 또 다른 상위 구조인 파드가 필요합니다.
쿠버네티스는 파드 안에 있는 모든 컨테이너가 자체 네임스페이스가 아닌 동일한 리눅스 네임스페이스를 공유하도록 도커를 설정합니다. 동일한 네트워크 네임스페이스, UTS 네임스페이스, IPC 네임스페이스 아래에서 실행됩니다. 하지만, 컨테이너 파일시스템은 컨테이너 이미지에서 나오므로 기본적으로 파일시스템은 다른 컨테이너와 완전히 분리됩니다.
파드 안의 컨테이너가 동일한 네트워크 네임스페이스에서 실행되기 때문에, 동일한 IP 주소와 포트 공간을 공유합니다. 따라서 동일한 파드 안 컨테이너에서 실행 중인 프로세스가 같은 포트 번호를 사용하지 않도록 주의해야 합니다. 또한, 파드 안에 있는 모든 컨테이너는 동일한 루프백 네트워크 인터페이스를 갖기에 로컬호스트로 컨테이너들이 서로 통신할 수 있습니다.
쿠버네티스 클러스터의 모든 파드는 하나의 플랫한 공유 네트워크 주소 공간에 상주합니다. 따라서 두 파드가 동일 혹은 서로 다른 워커 노드에 있는지는 중요하지 않으며, 두 경우 모두 파드 안에 있는 컨테이너는 NAT 없는 플랫 네트워크를 통해 서로 통신하는 것이 가능합니다.
레이블(labels)을 통해 파드와 기타 다른 쿠버네티스 오브젝트의 조직화가 이뤄집니다. 레이블은 리소스에 첨부하는 키-값 쌍으로, 레이블 셀렉터를 사용해 리소스를 선택할 때 활용됩니다.
쿠버네티스는 오브젝트를 겹치지 않는 그룹으로 분할하고자 오브젝트를 네임스페이스로 그룹화합니다. (리눅스 네임스페이스 X) 쿠버네티스 네임스페이스는 오브젝트 이름의 범위를 제공합니다. 리소스를 프로덕션, 개발, QA 환경등으로 나누어 사용할 수 있습니다. 리소스 이름은 네임스페이스 안에서만 고유하면 됩니다. 네임스페이스를 사용해 서로 관계없는 리소스를 겹치지 않는 그룹으로 분리할 수 있습니다. 네임스페이스는 리소스를 격리하는 것 외에도 특정 사용자가 지정된 리소스에 접근할 수 있도록 허용하고, 개별 사용자가 사용할 수 있는 컴퓨팅 리소스를 제한하는 데에도 사용됩니다.
라이브니스프로브를 통해 컨테이너가 살아 있는지 확인할 수 있습니다. HTTP GET 프로브는 서버가 오류 응답 코드를 반환하거나 전혀 응답하지 않으면 프로브가 실패한 것으로 간주돼 컨테이너를 다시 시작합니다. TCP 소켓 프로브는 컨테이너의 지정된 포트에 TCP 연결을 시도합니다. Exec 프로브는 컨테이너 내의 임의의 명령을 실행하고 명령의 종료 상태 코드를 확인합니다. initialDelaySeconds 속성을 라이브니스 프로브에 추가하면 요청을 보내기 전 초기 지연(대기) 시간을 설정할 수 있습니다. 운영 환경에서 실행 중인 파드는 반드시 라이브니스 프로브를 정의해야 합니다. 또한 라이브니스 프로브는 너무 많은 연산 리소스를 사용해서는 안 되며, 완료하는 데 너무 오래 걸리지 않아야 합니다. 라이브니스 프로브가 실패한 경우 쿠버네티스가 컨테이너를 재시작하는데, 이 작업은 파드를 호스팅하는 노드의 kubelet에서 수행합니다. 마스터의 쿠버네티스 컨트롤 플레인 구성 요소는 관여하지 않습니다. 하지만, 노드 자체에 크래시가 발생한 경우 노드 크래시로 중단된 모든 파드의 대체 파드를 생성하는 것은 컨트롤 플레인의 몫입니다.
레플리케이션컨트롤러
레플리케이션컨트롤러는 쿠버네티스 리소스로서 파드가 항상 실행되도록 보장합니다. 어떤 이유에서든 파드가 사라지면, 레플리케이션컨트롤러는 사라진 파드를 감지해 교체 파드를 생성합니다.
- 레이블 셀렉터(label selector)는 레플리케이션컨트롤러의 범위에 있는 파드를 결정한다.
- 레플리카 수(replica count)는 실행할 파드의 의도하는 수를 지정한다. 변경 시 기존 파드에 영향을 미친다.
- 파드 템플릿(pod template)은 새로운 파드 레플리카를 만들 때 사용한다.
일정 시간이 지난 후 또는 특정 이벤트가 발생한 후에 파드가 제대로 동작하지 않는 버그가 있다고 가정해봅시다. 파드가 오작동함을 안다면 파드를 레플리케이션컨트롤러의 범위 밖으로 빼내 컨트롤러가 새 파드로 교체하도록 한 다음, 원하는 방식으로 파드를 디버그하거나 문제를 재연해볼 수 있습니다.
레플리케이션컨트롤러의 파드 템플릿을 변경하면 변경 이후에 생성된 파드만 영향을 미치며 기존 파드는 영향을 받지 않습니다.
쿠버네티스에서 파드를 수평으로 확장한다는 것은 쿠버네티스에게 무엇을 어떻게 하라고 말하는 게 아니라 의도하는 상태를 지정할 뿐입니다.
레플리카셋
레플리카셋(ReplicaSet)은 차세대 레플리케이션컨트롤러며, 이를 완전히 대체할 것입니다. 일반적으로 레플리카셋을 직접 생성하지 않고, 디플로이먼트 리소스를 생성할 때 자동으로 생성되게 합니다.
레플리카셋은 레플리케이션컨트롤러오 똑같이 동작하지만 좀 더 풍부한 표현식을 사용하는 파드 셀렉터를 갖고 있습니다. 레플리카셋은 특정 레이블이 없는 파드나 레이블의 값과 상관없이 특정 레이블의 키를 갖는 파드를 매칭시킬 수 있습니다.
잡 리소스
잡(job)은 파드의 컨테이너 내부에서 실행 중인 프로세스가 성공적으로 완료되면 컨테이너를 다시 시작하지 않는 파드를 실행할 수 있습니다. 노드에 장애가 발생한 경우 해당 노드에 있던 잡이 관리하는 파드는 다른 노드로 다시 스케줄링됩니다. 프로세스 자체에 장애가 발생한 경우, 잡에서 컨테이너를 다시 시작할 것인지 설정할 수 있습니다.
쿠버네티스에서의 크론 작업은 크론잡(CronJob) 리소스를 만들어 구성합니다. 잡 실행을 위한 스케줄은 잘 알려진 크론 형식으로 지정합니다. 스케줄은 "분 시 일 월 요일" 5개의 항목을 갖고있습니다.
서비스
Why 서비스가 만들어졌나?
- 파드는 일시적이다. 파드가 공간확보를 위해 노드에서 제거되거나, 파드 수를 줄이거나, 클러스터 노드의 장애로 언제든 다른 노드로 이동할 수 있다.
- 쿠버네티스는 노드에 파드를 스케줄링한 후 파드가 시작되기 바로 전에 파드의 IP 주소를 할당한다. 따라서 클라이언트는 서버인 파드의 IP 주소를 미리 알 수 없다.
- 수평 스케일링은 여러 파드가 동일한 서비스를 제공할 수 있음을 의미한다. 각 파드는 고유한 IP 주소가 있다. 파드의 개별 IP 목록을 유지할 필요가 없다. 그 대신, 모든 파드는 단일 IP 주소로 액세스할 수 있어야 한다.
쿠버네티스 서비스는 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스 입니다. 각 서비스는 서비스가 존재하는 동안 불변하는 IP 주소와 포트가 있습니다. 클라이언트는 해당 IP와 포트로 접속 후 해당 서비스를 지원하는 파드 중 하나로 연결됩니다. 서비스의 클라이언트는 서비스를 제공하는 개별 파드 위치를 알 필요가 없으므로, 이 파드는 언제든지 클러스터 안에서 이동할 수 있습니다.
서비스를 지원하는 파드가 한 개 혹은 그 이상일 수 있습니다. 서비스 연결은 서비스 뒷단의 모든 파드로 로드밸런싱됩니다. 특정 클라이언트의 모든 요청을 매번 같은 파드로 리디렉션하려면 서비스의 세션 어피니티(sessionAffinity) 속성을 기본값 None 대신 ClientIP로 설정하면 됩니다. 쿠키 기반 세션 어피니티 옵션이 없는 이유는 서비스는 TCP와 UDP 패킷을 처리하고 그들이 가지고 있는 payload는 신경쓰지 않기 때문입니다. 쿠키는 HTTP 프로토콜의 구성이기 때문에 서비스는 쿠키를 알지 못하며, 세션 어피니티를 쿠키 기반으로 할 수 없는 이유입니다.
서비스는 파드에 직접 연결(link)되지 않고, 그 사이에 엔드포인트 리소스가 있습니다. 엔드포인트 리소스는 서비스로 노출되는 파드의 IP 주소와 포트 목록입니다. 파드 셀렉터는 서비스 스펙에 정의돼 있지만 들어오는 연결을 전달할 때 직접 사용하지는 않습니다. 대신 셀렉터는 IP와 포트 목록을 작성하는데 사용되며 엔드포인트 리소스에 저장됩니다. 클라이언트가 서비스에 연결하면 서비스 프록시는 이들 중 하나의 IP와 포트 쌍을 선택하고 들어온 연결을 대상 파드의 수신 대기 서버로 전달합니다.
외부 클라이언트에서 서비스를 액세스할 수 있는 방법은 다음과 같습니다.
- 노드포트로 서비스 유형 설정
- 서비스 유형을 노드포트 유형의 확장인 로드밸런스로 설정
- 단일 IP 주소로 여러 서비스를 노출하는 인그레스 리소스 만들기
노드포트 서비스를 만들면 쿠버네티스는 모든 노드에 특정 포트를 할당하고(모든 노드에서 동일한 포트 번호 사용) 서비스를 구성하는 파드로 들어오는 연결을 전달합니다. 이는 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스할 수 있어 노드포트 서비스와 상호작용할 때 큰 의미가 있습니다.
로드밸런서는 (클라우드 공급자에서 사용) 공개적으로 액세스 가능한 고유한 IP 주소를 가지며 모든 연결을 서비스로 전달합니다. 웹 브라우저에서 서비스에 액세스할 때 브라우저는 매번 정확히 같은 파드를 호출합니다. 세션 어피니티가 여전히 None 이지만, 브라우저는 keep-alive 연결을 사용하고 같은 연결로 모든 요청을 보내는 반면, curl은 매번 새로운 연결을 엽니다. 서비스는 연결 수준에서 동작하므로 서비스에 대한 연결을 처음 열면 임의의 파드가 선택된 다음 해당 연결에 속하는 모든 네트워크 패킷은 모두 같은 파드로 전송됩니다.
인그레스(Ingress)
로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드밸런서가 필요하지만, 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 가능하도록 지원해줍니다.(여러 서비스를 하나의 인그레스로 노출 가능) 클라이언트가 HTTP 요청을 인그레스에 보낼 때, 요청한 호스트와 path에 따라 요청을 전달할 서비스가 결정됩니다.
인그레스는 애플리케이션 계층에서 작동(L7)하며 서비스가 할 수 없는 쿠키 기반 세션 어피니티 등과 같은 기능을 제공할 수 있습니다.
인그레스 리소스를 작동시키려면 클러스터에 인그레스 컨트롤러를 실행해야 합니다.
아래 그림은 클라이언트가 인그레스 컨트롤러로 파드에 연결하는 방식을 보여줍니다.
클라이언트는 kubia.example.com의 DNS 조회를 수행 → DNS 서버가 인그레스 컨트롤러의 IP 반환 → 클라이언트는 HTTP 요청을 인그레스 컨트롤러로 전송, host 헤더에 kubia.example.com 지정 → 컨트롤러는 해당 헤더에서 클라이언트가 액세스 하려는 서비스 결정 → 서비스와 관련된 엔드포인트 오브젝트로 파드 IP 조회 → 클라이언트 요청을 파드에 전달
인그레스 컨트롤러는 요청을 서비스로 전달하지 않고 파드를 선택하는 데만 사용합니다.
인그레스 스펙을 자세히 보면 여러 호스트(host)와 경로(path)를 여러 서비스에 매핑할 수 있습니다. 따라서 클라이언트는 단일 IP 주소(인그레스 컨트롤러의 IP 주소)로 path를 달리해 두 개의 서비스에 도달할 수 있고, 다른 도메인네임을 모두 인그레스 컨트롤러의 IP 주소로 지정해 호스트 기반으로 서로 다른 서비스를 매핑할 수도 있습니다.
파드의 레이블이 서비스의 파드 셀렉터와 일치할 경우, 파드가 서비스의 엔드포인트로 포함됨을 아실 겁니다. 만약 적절한 레이블을 가진 새 파드가 만들어지자마자 서비스의 일부가 돼 요청이 파드로 전달되기 시작하면 어떻게될까요? 이러한 대참사를 방지하기 위해 완전히 준비될 때까지 기동 중인 파드에 요청을 전달하지 않는 방법이 있습니다.
레디니스 프로브
레디니스 프로브는 주기적으로 호출되며 특정 파드가 클라이언트 요청을 수신할 수 있는지 결정합니다. 파드 컨테이너의 레디니스 프로브는 파드를 서비스 엔드포인트에 포함해야 하는지 여부를 결정합니다. 애플리케이션 특성에 따라 상세한 레디니스 프로브를 작성할 수 있지만, 개발자의 몫입니다.
- Exec 프로브: 프로세스를 실행하는 Exec 프로브는 컨테이너의 상태를 프로세스의 종료 상태 코드로 결정합니다.
- HTTP GET 프로브: HTTP GET 요청을 컨테이너로 보내고 응답의 HTTP 상태 코드를 보고 컨테이너 준비여부를 결정합니다.
- TCP 소켓 프로브: 컨테이너의 지정된 포트로 TCP 연결을 엽니다. 소켓이 연결되면 컨테이너가 준비된 것으로 간주합니다.
레디니스 프로브는 라이브니스 프로브와 달리 컨테이너가 준비 상태 점검에 실패하더라도 컨테이너가 종료되거나 다시 시작되지 않습니다. 라이브니스 프로브는 상태가 안좋은 컨테이너를 제거하고 새로운 컨테이너로 교체해 파드의 상태를 정상으로 유지합니다. 반면, 레디니스 프로브는 요청을 처리할 준비가 된파드의 컨테이너만 요청을 수신하도록 합니다. 파드의 레디니스 프로브가 실패하면 파드는 엔드포인트 오브젝트에서 제거됩니다. 레디니스 프로브를 사용하면 클라이언트가 정상 상태인 파드하고만 통신해 시스템에 문제가 있다는 것을 알아차리지 못합니다.
파드의 각 컨테이너에 레디니스 프로브가 정의될 수 있습니다. 레디니스 프로브를 항상 정의해야 합니다. 이 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 돼 에러를 보게 될 수 있습니다. 또한, 레디니스 프로브에 파드의 종료 코드를 포함하지 말아야 합니다. 파드가 종료할 때, 실행되는 애플리케이션은 종료 신호를 받자마자 연결 수락을 중단합니다. 따라서 종료 절차가 시작되는 즉시 레디니스 프로브가 실행하도록 만들어 파드가 모든 서비스에서 확실하게 제거돼야 한다 생각할 수 있지만 그렇지 않습니다. 쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거합니다.
볼륨
앞서 파드와 상호작용하는 쿠버네티스 리소스들을 살펴봤습니다. 이제 컨테이너가 어떻게 외부 디스크 스토리지에 접근하는지, 어떻게 컨테이너 간에 스토리지를 공유하는지 살펴봅시다.
파드 내부의 각 컨테이너는 고유하게 분리된 파일시스템을 가집니다. 파일시스템은 컨테이너 이미지에서 제공되기 때문입니다.
쿠버네티스의 스토리지 볼륨은 파드의 일부분으로 정의되며 파드와 동일한 라이프사이클을 가집니다. 따라서 볼륨의 콘텐츠는 컨테이너를 다시 시작해도 지속되며, 새로운 컨테이너는 이전 컨테이너가 볼륨에 기록한 모든 파일들을 볼 수 있습니다. 또한, 파드가 여러 개의 컨테이너를 가진 경우 모든 컨테이너가 볼륨을 공유할 수 있습니다.
쿠버네티스 볼륨은 접근하려는 컨테이너에서 각각 마운트돼야 합니다. 각 컨테이너에서 파일시스템의 어느 경로에나 볼륨을 마운트할 수 있습니다. 컨테이너와 볼륨이 같은 파드에서 구성됐더라도 컨테이너에서 접근하려면 파드에서 볼륨을 정의하는 것만으로는 충분치 않고 VolumeMount를 컨테이너 스펙에 정의해야 합니다. 볼륨을 채우거나 마운트하는 프로세스는 파드의 컨테이너가 시작되기 전에 수행됩니다.
볼륨을 사용한 컨테이너 간 데이터 공유
- emptyDir 볼륨 사용: 볼륨이 빈 디렉토리로 시작되며 볼륨의 라이프사이클이 파드에 묶여 있으므로 파드가 삭제되면 볼륨의 콘텐츠는 사라집니다. 이는 동일 파드에서 실행 중인 컨테이너 간 파일을 공유할 때 유용합니다. 또한, 단일 컨테이너에서도 임시 데이터를 디스크에 쓰는 목적의 경우에 사용할 수 있습니다. emptyDir 볼륨은 가장 단순한 볼륨의 유형이지만, 다른 유형들도 이 볼륨을 기반으로 합니다. 빈 디렉토리가 생성된 후 데이터로 채워집니다.
- gitRepo 볼륨 사용: 컨테이너가 생성되기 전에 깃 리포지터리를 복제하고 특정 리비전을 체크아웃해 데이터로 채웁니다. gitRepo에 변경을 푸시할 때마다 웹사이트의 새 버전을 서비스하기 위해 파드를 삭제해줘야 하는 점이 단점입니다. emptyDir 볼륨과 유사하게 기본적으로 볼륨을 포함하는 파드를 위해 특별히 생성되고 독점적으로 사용되는 전용 디렉토리입니다.
- hostPath 볼륨 사용: hostPath 볼륨은 노드 파일시스템의 특정 파일이나 디렉토리를 가리킵니다. hostPath 볼륨은 워커 노드의 특정 파일이나 디렉토리를 컨테이너의 파일시스템에 마운트합니다. 퍼시스턴트 스토리지로, 파드가 종료되어도 hostPath 볼륨의 콘텐츠는 삭제되지 않습니다. 파드가 삭제되면 다음 파드가 호스트의 동일 경로를 가리키는 hostPath 볼륨을 사용하고, 이전 파드와 동일한 노드에 스케줄링된다는 조건에서 새로운 파드는 이전 파드가 남긴 모든 항목을 볼 수 있습니다.hostPath 볼륨은 파드가 어떤 노드에 스케줄링되느냐에 따라 민감하기 때문에 일반적인 파드에 사용하는 것은 좋은 생각이 아닙니다. 여러 파드에 걸쳐 데이터를 유지하는 목적으로 사용하지 마세요.
퍼시스턴트볼륨(PV), 퍼시스턴트볼륨클레임(PVC)
클러스터 관리자(Admin)이 퍼시스턴트볼륨을 프로비저닝하면 파드는 퍼시스턴트볼륨클레임을 통해 이를 사용합니다. 관리자가 기반 스토리지를 설정 후 쿠버네티스 API 서버로 PV 리소스를 생성해 쿠버네티스에 등록합니다. PV가 생성되면 관리자는 크기와 지원 가능한 접근 모드를 지정합니다.
클러스터 사용자(User)는 최소 크기와 필요한 접근 모드를 명시한 PVC 매니페스트를 생성합니다. 그런 다음 사용자는 PVC 매니페스트를 쿠버네티스 API 서버에 게시하고 쿠버네티스는 적절한 PV를 찾아 클레임에 볼륨을 바인딩합니다. 그 후 PVC는 파드 내부의 볼륨 중 하나로 사용될 수 있습니다. PVC의 바인딩을 삭제해 릴리즈될 때까지 다른 사용자는 동일한 PV를 사용할 수 없습니다.
PV는 파드나 PVC와 달리 특정 네임스페이스에 속하지 않고, 노드와 같은 클러스터 수준 리소스입니다. 파드가 재스케줄링되더라도 동일한 PVC가 사용 가능한 상태로 유지되기를 원하므로 PV에 대한 클레임은 파드를 생성하는 것과 별개의 프로세스입니다. PVC는 사용자가 PV에 하는 요청입니다. 쿠버네티스는 중간에 PVC를 두어 파드와 파드가 사용할 스토리지를 분리하는 전략을 취합니다.
PVC가 생성되자마자 쿠버네티스는 적절한 PV를 찾고 클레임에 바인딩합니다. PV의 용량은 PVC의 요청을 수용할만큼 충분히 커야 합니다. 추가로 볼륨 접근모드는 클레임에서 요청한 접근 모드를 포함해야 합니다. (볼륨을 동시에 사용할 수 있는 워커 노드 수와 관련된 접근모드 약어들이 존재합니다)
파드와 PVC를 삭제 후 PV를 조회해보면 Released 상태입니다. Available 상태가 아닌 이유는 이미 볼륨을 사용했기 때문에 데이터를 가지고 있고, 클러스터 관리자가 볼륨을 완전히 비우지 않으면 새로운 클레임에 바인딩할 수 없습니다. PV를 자동으로 다시 클레임하는 리클레임 정책에는 Recycle과 Delete가 존재합니다. Recycle은 현재는 유지보수가 중단됐습니다.
PV의 동적 프로비저닝을 통해 관리자가 많은 PV를 미리 프로비저닝 하는 대신 하나 혹은 그 이상의 스토리지클래스를 정의하면 시스템은 누군가 PVC를 통해 요청 시 새로운 PV를 생성합니다.
ConfigMap
컨피그맵은 컨테이너에 필요한 환경 설정을 컨테이너와 분리해서 제공하는 기능입니다.
컨피그맵을 사용해 설정 데이터를 저장할지 여부에 관계없이 다음 방법으로 애플리케이션을 구성할 수 있습니다.
- 컨테이너에 명령줄 인수 전달: 컨테이너에서 실행하는 전체 명령은 명령어와 인자, 두 부분으로 구성돼있습니다. Dockerfile에서 ENTRYPOINT는 컨테이너가 시작될 때 호출될 명령어를 정의합니다. CMD는 ENTRYPOINT에 전달되는 인자를 정의합니다. ENTRYPOINT 명령어로 실행하고 기본 인자를 정의하려는 경우에만 CMD를 지정하세요. 쿠버네티스에서는 command, args로 같은 기능을 수행합니다.
- 각 컨테이너를 위한 사용자 정의 환경변수 지정: 환경변수를 컨테이너 정의에 포함해 스크립트에 전달할 수 있습니다. 환경변수는 파드 레벨이 아닌 컨테이너 정의 안에 설정합니다. 각 컨테이너를 설정할 때, 쿠버네티스는 자동으로 동일한 네임스페이스 안에 있는 각 서비스에 환경변수를 노출합니다.
- 특수한 유형의 볼륨을 통해 설정 파일을 컨테이너에 마운트
애플리케이션 구성 요점은 환경에 따라 다르거나 자주 변경되는 설정 옵션을 애플리케이션 소스 코드와 별도로 유지하는 것입니다. 쿠버네티스에서는 설정 옵션을 컨피그맵이라 부르는 별도 오브젝트로 분리할 수 있습니다. 컨피그맵은 짧은 문자열에서 전체 설정 파일에 이르는 값을 가지는 키/값 쌍으로 구성된 맵입니다.
맵의 내용은 컨테이너 환경변수 또는 볼륨 파일로 전달됩니다. 또한 환경변수는 $(ENV_VAR) 구문으로 명령줄 인수에서 참조할 수 있기에 컨피그맵 항목을 프로세스의 명령줄 인자로 전달할 수도 있습니다. 컨피그맵을 컨테이너와 분리해 둠으로써 동일한 컨테이너를 개발용, 스테이지용, 서비스용으로 모두 사용하는 것이 가능해 집니다.
그러면 맵 값을 파드 안의 컨테이너에 전달할 수 있을까요?
- 환경변수를 컨피그맵에서 가져오는 파드 선언: valueFrom 필드로 참조하는 컨피그맵 이름과 키 이름으로 값을 가져올 수 있습니다.
- 컨피그맵의 모든 항목을 한 번에 환경변수로 전달: 파드 선언에서 envFrom 속성을 통해 컨피그맵의 모든 항목을 환경변수로 노출할 수 있습니다.
- 컨피그맵 항목을 명령줄 인자로 전달: 컨피그맵 항목을 환경변수로 먼저 초기화하고 인자로 참조하도록 지정할 수 있습니다. $(ENVVARIABLENAME) 문법을 사용해 쿠버네티스가 해당 변수의 값을 인자에 주입합니다.
- 컨피그맵 볼륨을 사용해 컨피그맵 항목을 파일로 노출: 컨피그맵 볼륨은 파일로 컨피그맵의 각 항목을 노출합니다.
시크릿
시크릿은 비밀번호, OAuth 토큰, SSH 키와 같은 민감한 정보들을 저장하는 용도로 사용합니다. 이러한 정보를 보관하고 배포하기 위해 쿠버네티스는 시크릿이라는 별도 오브젝트를 제공합니다.
시크릿은 키-값 쌍을 가진 맵으로 컨피그맵과 유사합니다. 시크릿은 컨피그맵과 같은 방식으로 사용할 수 있습니다.
- 환경변수로 시크릿 항목을 컨테이너에 전달
- 시크릿 항목을 볼륨 파일로 노출
쿠버네티스는 시크릿에 접근해야 하는 파드가 실행되고 있는 노드에만 개별 시크릿을 배포해 시크릿을 안전하게 유지합니다. 또한 노드 자체적으로 시크릿을 항상 메모리에만 저장되게 합니다.
모든 파드에는 secret 볼륨이 자동으로 연결돼 있습니다. 시크릿이 갖고 있는 세 가지 항목(ca.crt, namespace, token)은 파드 안에서 쿠버네티스 API 서버와 통신할 때 필요한 모든 것을 나타냅니다.
시크릿과 컨피그맵은 매우 큰 차이가 있습니다. 시크릿 항목의 내용은 Base64 인코딩 문자열로 표시되고, 컨피그맵의 내용은 일반 텍스트로 표시됩니다.
쿠버네티스 REST API
애플리케이션이 다른 리소스의 정보가 필요하거나 가능한 한 최신 정보에 접근해야하는 경우 API 서버와 직접 통신해야 합니다.
- kubectl 프록시로 API 서버 액세스하기
kubectl proxy 명령은 프록시 서버를 실행해 로컬 컴퓨터에서 HTTP 연결을 수신하고, 인증을 관리하며 API 서버로 연결을 전달하기 때문에, 요청할 때마다 인증 토큰을 전달할 필요가 없습니다. 각 요청마다 서버의 인증서를 확인해 실제 API 서버와 통신합니다.
- 파드 내에서 API 서버와 통신
kubectl이 없는 파드 내에서 통신하는 방법을 알아봅시다. 파드 내부에서 API 서버와 통신하려면 "API 서버 위치찾기, API 서버와 통신하고 있는지 확인, API 서버로 인증" 의 3가지를 처리해야 합니다.
- 앰배서더 컨테이너 패턴
API 서버로 직접 요청을 보내는 대신 프록시로 요청을 보내 인증, 암호화 및 서버 검증을 처리하게 합니다. API 서버와 직접 통신하는 대신 메인 컨테이너의 애플리케이션은 HTTPS 대신 HTTP로 앰배서더에 연결하고 앰배서더 프록시가 API 서버에 대한 HTTPS 연결을 처리하도록해 보안을 투명하게 관리할 수 있습니다. 컨테이너에서 일반 HTTP 요청을 앰배서더 컨테이너 내에 실행 중인 프록시로 전송한 다음, 프록시는 HTTPS 요청을 API 서버로 전송하며, 토큰을 전송해 클라이언트 인증을 처리하고 서버의 인증서를 검증해 서버의 신원을 확인합니다.
- 클라이언트 라이브러리를 사용해 API 서버와 통신
애플리케이션이 단순한 API 요청 이상을 수행하려면 쿠버네티스 API 클라이언트 라이브러리 중 하나를 사용하는 것이 좋습니다. Kubernetes API 클라이언트 라이브러리(Golang, Python, Java 등)는 일반적으로 HTTPS를 지원하고 인증을 관리하므로 앰배서더 컨테이너를 사용할 필요가 없습니다.
디플로이먼트
다른 파드나 외부 클라이언트에 서비스를 제공하는 파드 인스턴스 세트가 있다고 가정해봅시다. 파드가 레플리카셋을 지원하고, 클라이언트가 파드에 액세스하는 서비스도 있다고 가정해봅시다. 이미지 버전이 업데이트 돼 전체 파드를 업데이트 해야할 때 2가지 방법이 있습니다. 먼저, 기존 파드를 모두 삭제 후 다음 새 파드를 시작하기와 둘째, 새 파드를 시작하고 기동하면 기존 파드를 삭제하는 방법입니다.
- 오래된 파드를 삭제하고 새 파드로 교체: 레플리케이션 컨트롤러의 파드 템플릿은 언제든지 업데이트 할 수 있습니다. v1 파드 세트를 관리하는 레플리케이션컨트롤러를 이미지 버전 v2를 참조하도록 파드 템플릿을 수정한 다음 이전 파드 인스턴스들을 삭제해 쉽게 교체할 수 있습니다. 이전 파드가 삭제되고 새 파드가 시작되는 동안 짧은 시간의 다운타임이 발생합니다.
- 새 파드 기동과 이전 파드 삭제: 다운타임이 발생하지 않고 한 번에 여러 버전의 애플리케이션이 실행하는 것을 지원하는 경우 프로세스를 먼저 전환해 새 파드를 모두 기동 후 이전 파드를 삭제할 수 있습니다. 이에 따라 더 많은 하드웨어 리소스가 필요합니다. 파드 앞단의 서비스를 주목해봅시다. 새 버전을 실행하는 파드를 불러오는 동안 서비스는 파드의 이전 버전에 연결되고, 서비스의 레이블 셀렉터를 변경하고 서비스를 새 파드로 전환합니다. 이를 blue-green 디플로이먼트라고 합니다. 전환 후 이전 레플리케이션컨트롤러를 삭제합니다.
또한, 파드를 단계별로 교체하는 롤링 업데이트도 존재합니다. 이 경우 서비스의 파드 셀렉터에 이전 파드와 새 파드를 모두 포함해 요청을 두 파드 세트로 보낼 수 있습니다. 쿠버네티스는 kubectl rolling-update 명령어로 자동 롤링업데이트를 지원합니다. 하지만 kubectl rolling-update는 더이상 사용하지 않습니다. 그 이유는 저자 스스로 만든 오브젝트를 쿠버네티스가 수정하는 것을 막기 위해서입니다. 두 번째로, 롤링 업데이트의 모든 단계를 수행하는 것이 kubectl 클라이언트이기 때문입니다. 즉, 클라이언트가 업데이트 프로세스를 수행하면 네트워크 연결이 끊어졌을 때 업데이트 프로세스를 온전히 수행할 수 없기 때문입니다. 또한, desired state를 선언하는 것이 아닌 실제 명령을 내리는 것을 지양하기 때문입니다.
위와 같은 이유로 쿠버네티스에서는 파드 스펙에서 원하는 이미지 태그를 변경하고 쿠버네티스가 파드를 새 이미지로 실행하는 새로운 파드로 교체하는 디플로이먼트라는 리소스를 도입했습니다.
디플로이먼트는 레플리케이션컨트롤러 또는 레플리카셋을 통해 수행하는 대신 애플리케이션을 배포하고 선언적으로 업데이트하기 위한 high level의 리소스입니다.
디플로이먼트를 생성하면 레플리카셋 리소스가 그 아래에 생성됩니다. 디플로이먼트를 사용하면 레플리카셋에 의해 파드가 생성되고 관리됩니다. 애플리케이션을 업데이트 할 때에는 추가 레플리카셋을 도입하고 두 컨트롤러가 잘 조화되도록 조정하는 역할을 담당합니다.
디플로이먼트 생성
디플로이먼트는 레이블 셀렉터, 원하는 레플리카 수, 파드 템플릿으로 구성됩니다. 또한, 디플로이먼트 리소스가 수정될 때 업데이트 수행 방법을 정의하는 디플로이먼트 전략을 지정하는 필드도 존재합니다.
디플로이먼트가 레플리카셋을 사용해 파드를 만들 때 파드 이름은 "<디플로이먼트 이름>-<레플리카셋 해시값>-<파드 해시값>"을 의미합니다. 레플리카셋이 이러한 파드를 관리하고, 디플로이먼트는 파드를 직접 관리하지 않습니다.
디플로이먼트 업데이트
이전에는 레플리케이션컨트롤러(rc)로 쿠버네티스에 명시적으로 업데이트를 지시했습니다. 심지어 기존 rc를 대체하는 새로운 rc의 이름을 지정해야 했습니다.
디플로이먼트 업데이트에서는 디플로이먼트 리소스에 정의된 파드 템플릿을 수정하기만 하면 쿠버네티스가 실제 시스템 상태를 리소스에 정의된 상태로 만드는 데 필요한 모든 단계를 수행합니다.
디플로이먼트에 구성된 디플로이먼트 전략으로는 RollingUpdate(이전 버전과 새 버전 동시 실행가능 경우만 사용), Recreate(old 다 지우고 new 새로시작, 짧은 다운타임 발생) 이 있습니다.
디플로이먼트 리소스에서 파드 템플릿을 변경하는 것만으로 애플리케이션을 최신 버전으로 업데이트할 수 있습니다. kubectl 클라이언트가 프로세스를 수행하지 않고, 기존 레플리카셋도 삭제하지 않고 여전히 존재합니다.
디플로이먼트 롤아웃 이력 표시
디플로이먼트는 revision history를 유지하므로 롤아웃의 롤백이 가능합니다. 이력은 기본 레플리카셋에 저장됩니다. 롤아웃이 완료되면 이전 레플리카셋은 삭제되지 않으므로 이전버전뿐만 아니라 모든 버전으로 롤백할 수 있습니다. 개정 내역의 수는 디플로이먼트 리소스의 editionHistoryLimit 속성에 의해 제한됩니다. (기본값은 2)
- maxSurge: 디플로이먼트가 의도하는 레플리카 수보다 얼마나 많은 파드 인스턴스 수를 허용할 수 있는지 결정합니다.
- maxUnavailable: 업데이트 중에 의도하는 레플리카 수를 기준으로 사용할 수 없는 파드 인스턴스 수를 결정합니다.
예를 들어, maxSurge 1, maxUnavailable 1의 경우 기존 레플리카 수가 3으로 설정돼있다면 최소 2개의 파드는 사용할 수 있어야 합니다.
롤아웃 프로세스 일시 중지
롤아웃 프로세스를 일시 중지하는 기능도 있습니다. kubectl rollout pause 명령을 통해 롤아웃을 일시 중지할 수 있습니다. 새 파드 하나가 생성됐다고 가정하면, 요청의 일부가 새 파드로 전달됩니다. 이를 통해 카나리 릴리스를 효과적으로 실행할 수 있습니다. kubectl rollout resume 명령으로 멈췄던 롤아웃을 재개할 수 있습니다. 현재 카나리 릴리스를 수행하는 적절한 방법은 두 가지 다른 디플로이먼트를 사용해 적절히 확장하는 것입니다.
또한, 잘못된 버전의 롤아웃 방지 기능도 존재합니다.
minReadySeconds 속성은 파드를 available로 취급하기 전에 새로 만든 파드를 준비할 시간을 지정합니다. 파드가 사용 가능할 때까지 롤아웃 프로세스가 계속되지 않습니다. 모든 파드의 레디니스 프로브가 성공하면 파드가 준비됩니다. minReadySeconds가 지나기 전에 새 파드가 제대로 작동하지 않고 레디니스 프로브가 실패하기 시작하면 새 버전의 롤아웃이 효과적으로 차단됩니다.
적절한 minReadySeconds 설정과 적절히 구성된 레디니스 프로브는 쿠버네티스가 버그가 있는 버전을 배포하지 못하게 돕습니다.
참고자료
Kubernetes IN ACTION 도서
'Kubernetes' 카테고리의 다른 글
Kubernetes Operator (0) | 2022.11.24 |
---|---|
Helm 3.0 (0) | 2022.09.06 |