해당 포스팅은 "쿠버네티스 인 액션"을 공부하고 정리한 글입니다. 모든 내용은 해당 도서를 기준으로 합니다.
⬛ 5장 서비스:클라이언트가 파드를 검색하고 통신을 가능하게 함
쿠버네티스가 아닌 세계에서는
- 시스템 관리자가 클라이언트 구성 파일에 서비스를 제공하는 서버의 정확한 IP 주소나 호스트 이름을 지정해 각 클라이언트 애플리케이션을 구성함.
쿠버네티스에서는
- 동일한 작업을 수행하면 다음과 같은 이유로 동작하지 않음,
- 파드는 일시적임. 노드에서 제거되거나 누군가 파드 수를 줄이거나 클러스터 노드의 장애로 언제든 다른 노드로 이동할 수 있음.
- 쿠버네티스는 노드에 파다를 스케줄링한 후 파드가 시작되기 바로 전에 파드의 IP 주소를 할당함
- 클라이언트는 서버인 파드의 IP 주소를 미리 알 수 없음.
- 수평 스케일링은 여러 파드가 동일한 서비스를 제공할 수 있음을 의미함.
- 파드의 개별 IP 목록을 유지할 필요가 없지만 모든 파드는 단일 IP 주소로 액세스 할 수 있어야 함.
◼️ 5.1 서비스 소개
쿠버네티스의 서비스
- 동일한 서비스를 제공하는 파드 그룹에 지속적인 단일 접점을 만들려고 할 때 생성하는 리소스.
- 프론트엔드 웹 서버와 백엔드 데이터베이스 서버가 있는 예제
- 프론트엔드 역할 파드는 여러 개 있을 수 있지만 백엔드 데이터베이스 파드는 하나만 있음.
- 두가지 문제 해결 필요
- 외부 클라이언트는 프론트엔드 파드에 연결할 수 있어야 함.
- 프론트엔드 파드는 백엔드 데이터베이스에 연결해야 함. 데이터베이스가 파드 내에서 실행되기 때문에 시간이 지남에 따라 클러스터 주위를 이동해 IP 주소가 변경될 수 있음.
- 두가지 문제 해결 필요
- 프론트엔드 파드에 관한 서비스를 만들고 클러스터 외부에서 액세스할 수 있도록 구성하면 외부 클라이언트가 파드에 연결할 수 있는 하나의 고정 IP 주소가 노출됨.
- 백엔드 파드에 관한 서비스를 생성해 안정적인 주소를 만듦. 파드의 IP 주소가 변경되더라도 서비스의 IP 주소는 변경되지 않음.
- 서비스를 생성하면 프론트엔드 파드에서 환경변수 또는 DNS 이름으로 백엔드 서비스를 쉽게 찾을 수 있음.
▪️ 5.1.1 서비스 생성
서비스를 지원하는 파드가 한 개 혹은 그 이상일 수 있음.
레플리케이션컨트롤러와 기타 파드 컨트롤러에서 레이블 셀렉터를 사용해 동일한 세트에 속하는 파드를 지정하는 매커니즘을 서비스에도 그대로 사용함.
- Node.js 애플리케이션이 포함된 파드의 세 개의 인스턴스를 실행하는 레플리케이션컨트롤러를 만듦.
- 레플리케이션컨트롤러를 다시 생성하고 파드 인스턴스 세 개가 시작돼 실행 중인지 확인.
- 그런 다음 이 파드 세 개에 관한 서비스를 만듦.
서비스 생성 방법
- kubectl expose를 사용하는 것.
- expose 명령어는 레플리케이션컨트롤러에서 사용된 것과 동일한 파드 셀렉터를 사용해 서비스 리소스를 생성하고 모든 파드를 단일 IP 주소와 포트로 노출함.
클러스터 내에서 서비스 테스트
- 서비스의 클러스터 IP로 요청을 보내고 응답을 로그로 남기는 파드를 만드는 것. 그런 다음 파드의 로그를 검사해 서비스의 응답이 무엇인지 확인.
- 쿠버네티스 노드로 ssh 접속하고 curl 명령 실행.
- kubectl exec 명령어로 기존 파드에서 curl 명령 실행
실행 중인 컨테이너에 원격으로 명령어 실행
- kubectl exec 명령어를 사용하면 기존 파드의 컨테이너 내에서 원격으로 임의의 명령어를 실행할 수 있음.
kubectl exec "pod name" -- curl -s http://10.111.249.153
서비스의 세션 어피니티 구성
- 동일한 명령을 몇 번 더 실행하면 동일한 클라이언트에서 요청하더라도 서비스 프록시가 각 연결을 임의의 파드를 선택해 연결을 다시 forward하기 때문에 요청할 때마다 다른 파드가 선택됨.
- 특정 클라이언트의 모든 요청을 매번 같은 파드로 리디렉션하려면 서비스의 세션 어피니티 속성을 기본값 None 대신 ClientIP로 설정하면 됨.
- 이렇게 하면 서비스 프록시는 동일한 클라이언트 IP의 모든 요청을 동일한 파드로 전달함.
동일한 서비스에서 여러 개의 포트 노출
- 서비스는 단일 포트만 노출하지만 여러 포드를 지원할 수 있음.
- 파드가 두 개의 포드틑 수신한다면 하나의 서비르를 사용해 각 포트를 각 파드의 포트로 전달할 수 있음.
- 굳이 두 개의 서비스를 만들지 않아도 멀티 포트 서비스를 사용한다면 단일 클러스터 IP로 모든 서비스 포트가 노출됨.
▪️ 5.1.2 서비스 검색
환경변수를 통한 서비스 검색
- 파드가 시작되면 쿠버네티스는 해당 시점에 존재하는 각 서비스를 가리키는 환경변수 세트를 초기화 함.
- 클라이언트 파드를 생성하기 전에 서비스를 생성하면 해당 파드의 프로세스는 환경변수를 검사해 서비스의 IP 주소와 포트를 얻을 수 있음.
- 서비스에 대한 환경변수를 보려면
- 먼저 모든 파드를 삭제
- 레플리케이션컨트롤러에서 새로 파드 생성
- 새 파드 조회 후 파드를 kubectl exec 명령어의 대상으로 선택
- 대상 파드 선택 후 컨테이너 내부에서 env 명령어 실행해 환경변수 조회
DNS를 통한 서비스 검색
- 파드에서 실행 중인 프로세스에서 수행된 모든 DNS 쿼리는 시스템에서 실행 중인 모든 서비스를 알고 있는 쿠버네티스의 자체 DNS 서버로 처리됨.
- 각 서비스는 내부 DNS 서버에서 DNS 항목을 가져오고 서비스 이름을 알고 있는 클라이언트 파드는 환경변수 대신 FQDN(정규화된 도메인 이름)으로 액세스 할 수 있음.
서비스 IP에 핑을 할 수 없는 이유
- 서비스의 클러스터 IP가 가상 IP이므로 서비스 포트와 결합된 경우에만 의미가 있기 때문.
◼️ 5.2 클러스터 외부에 있는 서비스 연결
서비스가 클러스터 내에 있는 파드로 연결을 전달하는게 아니라 외부 IP와 포트로 연결을 전달하는 것
▪️ 5.2.1 서비스 엔드포인트 소개
서비스는 파드에 직접 연결되지 않고 엔드포인트 리로스가 그 사이에 있는 것.
서비스에서 kubectl describe 명령을 사용하면 엔드포인트를 확인할 수 있음.
엔드포인트 리소스
- 다른 쿠버네티스 리소스와 유사하므로 kubectl get을 사용해 기본 정보를 표시할 수 있음.
kubectl get endpoints "service name"
- 파드 셀렉터는 서비스 스펙에 정의돼 있지만 들어오는 연결을 전달할 때 직접 사용하지 않음.
- 셀렉터는 IP와 포트 목록으 ㄹ작성하는데 사용되며 엔드포인트 리소스에 저장됨.
- 클라이언트가 서비스에 연결하면 서비스 프록시는 이들 중 하나의 IP와 포트 쌍을 선택하고 들어온 연결을 대상 파드의 수신 대기 서버로 전달함.
▪️ 5.2.2 서비스 엔드포인트 수동 구성
파드 셀렉터 없이 서비스를 만들면 쿠버네티스는 엔드포인트 리소스를 만들지 못함.
수동으로 관리되는 엔드포인트를 사용해 서비스를 만들려면 서비스와 엔드포인트 리소스를 모두 만들어야 함.
셀렉터가 없는 서비스에 관한 엔드포인트 리소스 생성
- 엔드포인트는 별도의 리소스이며 서비스 속성은 아님.
- 셀렉터가 없는 서비스를 생성했기 때문에 엔드포인트 리소스가 자동으로 생성되지 않음.
- 엔드포인트 오브젝트는 서비스와 이름이 같아야 하고 서비스를 제공하는 대상 IP 주소와 포트 목록을 가져야 함.
- 서비스와 엔드포인트 리소스가 모두 서버에 게시되면 파드 셀렉터가 있는 일반 서비스처럼 서비스를 사용할 수 있음.
- 만들어진 컨테이너는 서비스의 환경변수가 포함되며 IP:포트 쌍에 대한 모든 연결은 서비스 엔드포인트 간에 로드밸런싱함.
- 외부 서비스를 쿠버네티스 내에서 실행되는 파드로 마이그레이션하기로 결정한 경우 서비스에 셀렉터를 추가해 엔드포인트를 자동으로 관리할 수 있음.
- 서비스에서 셀렉터를 제거하면 쿠버네티스는 엔드포인트 업데이트를 멈춤.
- 서비스의 실제 구현이 변경되는 동안에도 서비스 IP 주소가 일정하게 유지될 수 있음을 의미함.
▪️ 5.2.3 외부 서비스를 위한 별칭 생성
ExternalName 서비스 생성
- 외부 서비스의 별칭으로 사용되는 서비스를 만들려면 type 필드를 ExternalName으로 설정해 서비스 리소스를 생성하면 됨.
- 서비스가 생성되면 파드는 서비스의 FQDN을 사용하는 대신 external-service.default.svc.cluster.local 도메인 이름으로 외부 서비스에 연결할 수 있음.
- 서비스를 사용하는 파드에서 실제 서비스 이름과 위치가 숨겨져 나중에 externalName 속성을 변경하거나 type을 다시 ClusterIP로 변경하고 서비스 스펙을 만들어 서비스 스펙을 수정하면 나중에 다른 서비스를 가리킬 수 있음.
- ExternalName 서비스는 DNS 레벨에서만 구현됨
- 서비스에 연결하는 클라이언트는 서비스 프록시를 완전히 무시하고 외부 서비스에 직접 연결함.
- 때문에 ExternalName type의 서비스는 ClusterIP를 얻지 못함.
◼️ 5.3 외부 클라이언트에 서비스 노출
- 노드 포트로 서비스 유형 설정
- 각 클러스터 노드는 노드 자체에서 포트를 열고 해당 포트로 수신된 트래픽을 서비스로 전달.
- 이 서비스는 내부 클러스터 IP와 포트로 액세스 가능, 모든 노드의 전용 포트로도 액세스 가능.
- 서비스 유형을 노드포트 유형의 확장인 로드밸런서로 설정
- 프로비저닝된 전용 로드밸런서로 서비스에 액세스할 수 있음.
- 로드밸런서는 트래픽을 모든 노드의 노드포트로 전달함.
- 클라이언트는 로드밸런서의 IP로 서비스에 액세스 함.
- 단일 IP 주소로 여러 서비스를 노출하는 인그레스 리소스 만들기
- HTTP 레벨에서 작동하므로 4계층 서비스보다 더 많은 기능을 제공할 수 있음.
▪️ 5.3.1 노드포트 서비스 사용
노드포트 서비스 생성 시
- 쿠버네티스는 모든 노드에 특정 포트를 할당
- 서비스를 구성하는 파드로 들어오는 연결 전달
- 서비스의 내부 클러스터 IP뿐만 아니라 모든 노드의 IP와 할당된 노드포트로 서비스에 액세스 할 수 있음.
노드포트 서비스 생성
- 두 클러스터 노드의 포드 30123에 노출된 서비스.
- 이런 포트에 대한 수신 연결은 임의로 선택된 파드로 전달.
- 연결 중인 노드에서 실행 중인 포트일 수도 있고 아닐 수도 있음.
- 첫번째 노드의 포트 30123에서 수신된 연결은 첫번째 노드에서 실행 중인 파드 또는 두번째 노드에서 실행 중인 파드로 전달될 수 있음.
- 외부 클라이언트가 노드포트 서비스에 액세스 할 수 있도록 방화벽 규칙 변경을 해줘야 함.
- 클라이언트가 첫번째 노드에만 요청하면 해당 노드가 장애가 나면 클라이언트는 더 이상 서비스에 액세스 할 수 없음.
- 모든 노드에 요청을 분산시키고 해당 시점에 오프라인 상태인 노드로 요청을 보내지 않도록 노드 앞에 로드밸런서를 배치하는 것이 좋음.
- 쿠버네티스 클러스터가 이를 지원하는 경우 노드포트 서비스 대신 로드밸런서를 생성해 로드밸런서를 자동으로 프로비저닝할 수 있음.
▪️ 5.3.2 외부 로드밸런서로 서비스 노출
로드밸런서
- 공개적으로 액세스 가능한 고유한 IP 주소를 가지며 모든 연결을 서비스로 전달함.
- 로드밸런서의 IP 주소로 서비스에 액세스 가능.
로드밸런서 서비스 생성
로드밸런서를 통한 서비스 연결
- 서비스 생성 후 클라우드 인프라가 로드밸런서를 생성하고 IP 주소를 서비스 오브젝트에 씀.
- 완료되면 로드밸런서 IP 주소가 서비스의 externalIP 주소로 표시 됨.
- 노드포트 서비스와 달리 방화벽 설정 필요 없음.
파드에 HTTP 요청이 전달되는 방법
- 외부 클라이언트는 로드밸런서의 포트에 80을 연결하고 노드에 암묵적으로 할당된 노드포트로 라우팅 됨.
- 여기에서 연결은 파드 인스턴스로 전달.
- 노드포트 서비스에 대한 포트의 방화벽을 여는 경우 노드 IP로도 서비스에 액세스 할 수 있음.
▪️ 5.3.3 외부 연결의 특성 이해
불필요한 네트워크 홉의 이해와 예방
- 외부의 연결을 수신한 노드에서 실행 중인 파드로만 외부 트래픽을 전달하도록 서비스를 구성해 추가 홉을 방지할 수 있음.
- 서비스의 스펙 섹션의 externalTrafficPolicy 필드를 설정하면 됨.
- 서비스 정의에 이 설정이 포함돼 있고 서비스의 노드포트로 외부 연결이 열린 경우 서비스 프록시는 로컬에 실행 중인 파드를 선택함.
- 로컬 파드가 존재하지 않으면 연결이 중단됨.
- 로드밸런서는 그러한 파드가 하나 이상 있는 노드에만 연결을 전달하도록 해야 함.
- 로드밸런서가 두 노드에 걸쳐 연결을 균등하게 분산하면 위처럼 노드 A는 파드는 모든 연결의 50%를 수신하지만 노드 B의 두 파드는 각각 25%만 수신함.
클라이언트 IP가 보존되지 않음 인식
- 노드포트로 연결을 수신하면 패킷에서 소스 네트워크 주소 변환(SNAT)이 수행되므로 패킷의 소스 IP가 변경됨.
- 파드는 실제 클라이언트 IP를 볼 수 업음.
- 로컬 외부 트래픽 정책은 연결을 수신하는 노드와 대상 파드를 호스팅하는 노드 사이에 추가홉이 없기 때문에 클라이언트 IP 보존에 영향을 미침(SNAT가 수행되지 않음).
◼️ 5.4 인그레스 리소스로 서비스 외부 노출
인그레스가 필요한 이유
- 로드밸런서 서비스는 자신의 공용 IP 주소를 가진 로드밸런서가 필요하지만 인그레스는 한 IP 주소로 수십 개의 서비스에 접근이 간으하도록 지원해 줌.
- 클라이언트가 HTTP 요청을 인그레스에 보낼 때 요청한 호스트와 경로에 따라 요청을 전달할 서비스가 결정됨.
- 인그레스는 네트워크 스택의 애플리케이션 계층(HTTP)에서 작동하며 서비스가 할 수 없는 쿠키 기반 세션 어피니티 등과 같은 기능을 제공함.
인그레스 동작 방식
- 클라이언트가 DNS 조회 수행.
- DNS 서버가 인그레스 컨트롤러의 IP 반환.
- 클라이언트는 HTTP 요청을 인그렛 컨트롤러로 전송하고 host 헤더에서 주소 지정.
- 컨트롤러는 해당 헤더에서 클라이언트가 액세스 하려는 서비스를 결정하고 서비스와 관련된 엔드포인트 오브젝트로 파드 IP를 조회한 다음 클라이언트 요청을 파드에 전달.
- 인그레스 컨트롤러는 요청을 서비스로 전달하지 않음. 파드를 선택하는데만 사용.
◼️ 5.5 파드가 연결을 수락할 준비가 됐을 때 신호 보내기
▪️ 5.5.1 레디니스 프로브 소개
레디니스 프로브
- 주기적으로 호출되며 특정 파드가 클라이언트 요청을 수신할 수 있는지 결정.
- 컨테이너의 레디니스 프로브가 성공을 반환하면 컨테이너가 요청을 수락할 준비가 됐다는 신호.
- 프로세스를 실행하는 Exec 프로브
- 컨테이너의 상태를 프로세스의 종료 상태 코드로 결정.
- HTTP GET 프로브
- HTTP GET 요청을 컨테이너로 보내고 응답의 HTTP 상태 코드를 보고 컨테이너가 준비됐는지 여부를 결정.
- TCP 소켓 프로브
- 컨테이너의 지정된 포트로 TCP 연결을 열고 소켓이 연결되면 컨테이너가 준비된 것으로 간주.
레디니스 프로브 동작
- 컨테이너 시작 시 쿠버네티스는 첫번째 레디니스 점검을 수행하기 전에 구성 가능한 시간이 경과하기를 기다리도록 구성.
- 주기적으로 프로브를 호출하고 레디니스 프로브의 결과에 따라 작동함.
- 파드가 준비 되지 않았다고 하면 서비스에서 제거됨.
- 파드가 다시 준비되면 서비스에 다시 추가됨.
- 컨테이너가 준비 상태 점검에 실패하더라도 컨테이너가 종료되거나 다시 시작되지 않음.
- 요청을 처리할 준비가 된 파드의 컨테이너만 요청을 수신하도록 함.
- 파드의 레디니스 프로브가 실패하면 파드는 엔드포인트 오브젝트에서 제거됨.
- 서비스로 연결하는 클라이언트 요청은 파드로 전달되지 않음.
- 파드의 레이블이 서비스의 레이블 셀렉터와 일치하지 않을 때와 같은 효과.
레디니스 프로브가 중요한 이유
- 파드가 특정 요청을 처리할 준비가 되지 않았다는 신호를 레디니스 프로브가 쿠버네티스에게 알리는 것이 현명할 수 있음.
- 다른 파드 인스턴스에 동일한 유형의 연결 문제가 발생하지 않으면 정상적으로 요청을 처리할 수 있음.
- 클라이언트가 정상 상태인 파드하고만 통신하고 시스템에 문제가 있다는 것을 절대 알아차리지 못함.
▪️ 5.5.3 실제 환경에서 레디니스 프로브가 수행해야 하는 기능
- 애플리케이션이 클라이언트 요청을 수신할 수 있는지 여부에 따라 성공 또는 실패를 반환해야 함.
- 서비스에서 파드를 수동으로 제거하려면 수동으로 프로브의 스위치를 전환하는 대신 파드를 삭제하거나 파드 레이블을 변경해야 함.
레디니스 프로브를 항상 정의
- 파드에 레디니스 프로브를 추가하지 않으면 파드가 시작하는 즉시 서비스 엔드포인트가 됨.
레디니스 프로브에 파드의 종료 코드 포함 하지 말 것
- 쿠버네티스는 파드를 삭제하자마자 모든 서비스에서 파드를 제거하기 때문.
◼️ 5.6 헤드리스 서비스로 개별 파드 찾기
클라이언트가 모든 파드에 연결 하려면?
- 각 파드의 IP를 알아야 하는데 클라이언트가 쿠버네티스 API 서버를 호출해 파드와 IP 주소 목록을 가져오도록 하는 것은 바람직하지 않음.
- 클라이언트가 DNS 조회로 파드 IP를 찾을 수 있음.
- 일반적으로 서비스에 대한 DNS 조회를 수행하면 DNS 서버는 하나의 IP를 반환함.
- 쿠버네티스 서비스에 클러스터 IP가 필요하지 않다면 DNS 서버는 하나의 서비스 IP 대신 파드 IP들을 반환함.
DNS 서버
- 하나의 DNS A 레코드를 반환하는 대신 서비스에 대한 여러 개의 A레코드를 반환함.
- 각 레코드는 해당 시점에 서비스를 지원하는 개별 파드의 IP를 가리킴.
- 클라이언트는 간단한 DNS A 레코드 조회를 수행, 서비스에 포함된 모든 파드의 IP를 얻을 수 있음.
- 클라이언트는 해당 정보를 사용해 하나 혹은 다수의 또는 모든 파드에 연결할 수 있음.
▪️ 5.6.2 DNS로 파드 찾기
헤드리스 서비스
- 일반 서비스와 다르게 보일 수 있지만 클라이언트 관점에서는 다르지 않음.
- 클라이언트는 일반 서비스와 마찬가지로 서비스의 DNS 이름에 연결해 파드에 연결할 수 있음.
- 헤드리스 서비스에서는 DNS가 파드의 IP를 반환하기 때문에 클라이언트는 서비스 프록시 대신 파드에 직접 연결함.
◼️ 5.7 서비스 문제 해결
서비스로 파드에 액세스 할 수 없는 경우
- 먼저 외부가 아닌 클러스터 내에서 서비스의 클러스터 IP에 연결되는지 확인.
- 서비스에 액세스 할 수 있는지 확인하려고 서비스 IP로 핑을 할 필요 없음.
- 레디니스 프로브를 정의했다면 성공했는지 확인.
- 파드가 서비스의 일부인지 확인하려면 kubectl get endpoints를 사용해 해당 엔드포인트 오브젝트를 확인.
- FQDN이나 그 일부로 서비스에 액세스 하려는데 작동안하면 FQDN 대신 클러스터 IP를 사용해 액세스 할 수 있는지 확인.
- 대상 포트가 아닌 서비스로 노출된 포트에 연결하고 있는지 확인.
- 파드 IP에 직접 연결해 파드가 올바른 포트에 연결돼 있는지 확인.
- 파드 IP로 애플리케이션에 액세스할 수 없는 경우 애플리케이션이 로컬호스트에만 바인딩하고 있는지 확인.