無知

갈 길이 먼 공부 일기

기술 공부/쿠버네티스

쿠버네티스 (11) | 쿠버네티스 내부 이해

moozii 2022. 3. 10. 18:12
앞으로의 스터디 내용은 <Kubernetes in Action>을 기반으로 진행합니다.
자세한 내용은, 해당 책을 확인해주세요! 
http://www.yes24.com/Product/Goods/89607047 

 

https://kubernetes.io/ko/docs/concepts/overview/components/

아키텍처 개괄

  • 쿠버네티스 컨트롤 플레인 : 클러스터 기능의 제어, 클러스터 상태의 저장 및 관리.
    • etcd 분산 저장 스토리지 : API 서버의 실패 여부와 무관히 안정성을 유지하기 위해, 매니페스트를 영구적으로 저장하는 목적으로, 분산된 키-값 저장소 역할의 etcd를 사용. 현재 기준 v3는 키를 계층적 키 공간에 저장해 키-값 쌍을 만들지만, v2와 달리 디렉터리를 지원하지 않음. 모든 데이터는 /registry에 저장되므로 키 목록을 확인 가능. 고가용성을 위해 2개 이상의 etcd 인스턴스를 실행. RAFT 합의 알고리즘으로 대다수의 노드가 동의하는 실제 상태의 합의를 진행. 합의 알고리즘에 따라 클러스터의 상태 변경 요청에서 과반을 충족해야 변경 가능. 과반성 충족 및 합의 조건을 위해, 노드의 수는 홀수로 5,7대 정도로 설정하는 것이 일반적. 
      • 추가 자료 : RAFT 합의 알고리즘이란? : Raft is a consensus algorithm that is designed to be easy to understand. It's equivalent to Paxos in fault-tolerance and performance. The difference is that it's decomposed into relatively independent subproblems, and it cleanly addresses all major pieces needed for practical systems. We hope Raft will make consensus available to a wider audience, and that this wider audience will be able to develop a variety of higher quality consensus-based systems than are available today.
        https://raft.github.io/ 
        분산 환경에서는 컴퓨팅에 참여하는 모든 노드가 현재 상태를 동일하게 유지하는 것이 중요합니다. 분산 환경에서 이런 합의를 위한 프로토콜로 가장 오래 사용되었던 것은 Paxos였으나, 스펙이 복잡하고 구현이 힘들다는 단점이 있었습니다. 이에 대한 대안으로, 스펙상 몇 가지 제약을 걸어 구조를 단순화하여 나온 것이 Raft입니다. (중략) Raft는 전술했듯이 분산 환경에서의 합의를 위한 방법은 투표(voting)와 로그 엔트리 복제로 크게 나뉜다. 참여하는 노드들은 투표를 통해 과반의 투표를 얻은 leader를 선출하고, 변경 내역은 leader를 통해서만 follower에게 전달하여 반영된다.
        https://d2.naver.com/helloworld/5663184
    • API 서버 : 각 시스템 구성 요소의 통신은 모두 API 서버로 일원화 (낙관적 잠금 메커니즘을 구현해 클러스터 상태를 업데이트하므로 일관성을 유지). API 서버는 etcd와 통신하는 유일한 구성 요소. API 서버로 etcd에 간접적으로 데이터 조회 및 작성. RESTful API로 CRUD 인터페이스를 제공해, 상태를 etcd에 저장. 오브젝트 변경이 있을 시 클라이언트가 API 서버에 HTTP 연결을 맺고 변경을 감지. etcd가 API 서버로 변경 사항을 통보하면, 모든 관찰자들, 여러 클라이언트들에게 갱신된 사항을 전달. 

      "쿠버네티스는 선언적 방식으로 프로그래밍한다. 파드 생성 명령이 들어오면, API 서버가 요청을 받아, ETCD에 그 요청 내용을 기록하여 선언하기만 하면 알아서 실행되는 방식이다."

      1. API 서버는 클라이언트로부터 요청을 받게 되면, 먼저 인증 플러그인을 활용해 요청을 보낸 클라이언트를 인증한다. 인증 방법은 클라이언트 인증서 혹은 HTTP 헤더에서 가져온다. 플러그인은 인증 작업에서 클라이언트 사용자 이름 및 아이디, 그룹 정보 등을 추출한다.
      2. API 서버는 인증 작업 이후 인가 작업을 인가 플러그인을 통해 수행한다. 인증된 사용자가 요청한 작업이, 요청한 리소스를 대상으로 수행 가능한지를 판별하는 단계이다. 
      3. API 서버는 인가 작업 이후, 요청 작업이 리소스 생성/수정/삭제일 경우 어드미션 컨트롤 플러그인(아래 쪽에 설명 있음)으로 해당 요청을 보낸다. 요청 내 리소스를 수정하고, 누락 필드를 기본값으로 초기화하거나 재수정하는 등의 작업을 거친다. (읽기 작업은 어드미션 컨트롤 작업을 거치지 않음)
      • 추가 자료 : 낙관적 잠금 메커니즘이란? : 
        낙관적 잠금이란 데이터 갱신시 충돌이 발생하지 않을 것이라고 낙관적으로 보고 잠금을 거는 기법입니다.
        https://velog.io/@lsb156/JPA-Optimistic-Lock-Pessimistic-Lock

        비관적 동시성은 데이터 소스의 행을 잠가 다른 사용자가 데이터를 수정하더라도 현재 사용자에게 영향을 미치지 못하도록 합니다
        . 비관적 모델에서 잠금이 적용되는 작업을 수행하면, 해당 잠금 소유자가 잠금을 해제하기 전까지 다른 사용자는 잠금과 충돌하는 작업을 수행할 수 없습니다. 이 모델은 동시성 충돌이 발생하는 경우 트랜잭션 롤백에 필요한 비용보다 잠금을 통해 데이터를 보호하는 비용이 적게 들도록 데이터 경합이 높은 환경에서 주로 사용됩니다. 따라서 비관적 동시성 모델에서는 행을 업데이트하는 사용자가 잠금을 설정합니다. 이 사용자가 업데이트 작업을 마치고 잠금을 해제할 때까지 다른 사용자는 해당 행을 변경할 수 없습니다. 이러한 이유로 비관적 동시성은 레코드를 프로그래밍 방식으로 처리하는 경우와 같이 잠금 시간이 짧은 경우에 가장 잘 구현됩니다. 비관적 동시성 모델은 사용자가 데이터와 상호 작용하여 비교적 오랜 시간 레코드를 잠그는 경우에는 사용하지 않는 것이 좋습니다. 동일한 작업에서 여러 행을 업데이트해야 하는 경우에는 비관적 잠금을 사용하는 것보다는 트랜잭션을 만드는 것이 훨씬 확장성이 좋습니다.
        비관적 동시성과 달리, 낙관적 동시성의 사용자는 특정 행을 읽을 때 행을 잠그지 않습니다. 이 경우 사용자가 행을 업데이트할 때 행을 읽은 후 다른 사용자가 해당 행을 변경했는지 여부를 애플리케이션에서 확인해야 합니다. 낙관적 동시성은 주로 데이터 경합이 낮은 환경에서 사용됩니다. 레코드를 잠그려면 서버 리소스가 추가로 소요되기 때문에, 낙관적 동시성을 사용하면 레코드를 잠글 필요가 없어 성능이 향상됩니다. 또한 레코드 잠금을 유지하기 위해서는 데이터베이스 서버와의 연결이 유지되어야 하는데, 낙관적 동시성 모델에서는 그럴 필요가 없기 때문에 짧은 시간에 더 많은 클라이언트가 서버에 연결할 수 있습니다.
        https://docs.microsoft.com/ko-kr/dotnet/framework/data/adonet/optimistic-concurrency
    •  
    • 스케줄러 : 파드 실행 시 클러스터 노드를 지정. API 서버의 감시 메커니즘을 통해 신규 파드 생성을 감지해, 노드에 할당. API 서버를 활용해 파드 정의를 개시하면, API 서버가 워커 노드 kubelet에 스케줄링을 통보하여, kubelet이 파드 컨테이너 생성 및 실행을 진행. 

      스케줄링 알고리즘 1. 모든 노드 중 파드 스케줄링이 가능한, 수용 가능 노드 검색. 조건 함수를 통해 하드웨어 리소스 요청 사항 출족 여부, 노드 내 리소스 부족 여부, 파드의 특정 노드 이름 지정 여부, 노드 셀렉터 레이블 일치 여부, 특정 유형 볼륨 지정 시 마운트 가능 여부, 노드의 테인트 허용 여부(테인트 추후 설명 예정), 어피니티 혹은 안티-어피니티 규칙 지정 여부 등을 확인. 
      스케줄링 알고리즘 2. 노드 중 우선순위를 지정해 최상위 노드를 선택. (동일 점수 시 모든 노드에 고르게 배포되도록 라운드-로빈을 채택)

      고급 파드 스케줄링 : 여러 노드에 파드를 분산하기 위해 어피니티, 안티-어피니티 규칙 정의로 클러스터 분산 정도를 강제
      다중 스케줄러 사용 : 여러 스케줄러를 사용하고, 파드 정의 내 스케줄러 지정을 진행. 
  • 컨트롤러 매니저 : 배포된 리소스의 변경 사항을 관찰하고, API 서버의 통보를 받음. API 서버로 배포된 리소스에 지정된대로 시스템이 수렴하도록 하는 활성 구성 요소이나 kubelet과 직접 통신하지 않음. 리소스 배포 시 실제 작업을 수행. 리소스 생성, 리소스 갱신 등. 조정 루프를 통해 spec에 따른 원하는 상태로 조정하고 신규 상태를 status 값에 기록하는 역할. 

    컨트롤러 목록 : 레플리케이션 매니저(rc용 컨트롤러), 레플리카셋/데몬셋/잡 컨트롤러, 디플로이먼트 컨트롤러, 스테이트풀셋 컨트롤러, 노드 컨트롤러, 서비스 컨트롤러, 엔드포인트 컨트롤러, 네임스페이스 컨트롤러, 퍼시스턴트볼륨 컨트롤러 등
    • 레플리케이션 매니저 : RC, 레플리케이션 컨트롤러 리소스를 조정. API 서버를 통해 변경을 감시하는 감시 메커니즘으로 변화를 수신 후 동작을 수행. 파드를 실행하지 않고 파드 정의를 API 서버에 게시해 kubelet이 생성 및 실행을 진행.
    • 레플리카셋/데몬셋/잡 컨트롤러 : 앞선 레플리케이션 매니저와 동일.
    • 디플로이먼트 컨트롤러 : 디플로이먼트 오브젝트 수정 시마다 신규 버전을 롤아웃하는데, 정의된 롤아웃 전략에 따라 조절. 
    • 스테이트풀셋 컨트롤러 : 다른 것과 유사하나, 다른 컨트롤러와 달리 파드 외로 퍼시스턴트볼륨 클레임도 인스턴스화해 관리하는 것이 차이점.
    • 노드 컨트롤러 : 특이 사항 없음
    • 서비스 컨트롤러 : 로드밸런서 유형 서비스 생성 및 삭제 시 로드밸런서 요청 및 해제 역할 수행.
    • 엔드포인트 컨트롤러 : 레이블 셀렉터와 일치하는 파드 IP와 포트로 엔드포인트 리스트를 지속 갱신. 서비스와 파드의 변경 여부를 항상 감시함. 컨트롤러가 필요한 경우 직접 엔드포인트 리소스를 생성하기도 하고, 서비스 삭제 시 엔드포인트도 함께 삭제. 
    • 네임스페이스 컨트롤러 : 네임스페이스 삭제 시 그 안의 모든 리소스 전체 삭제를 진행.
    • 퍼시스턴트볼륨 컨트롤러 : 적절한 퍼시스턴트 볼륨을 찾아 클레임과 연결해주는 역할. 퍼시스턴트 볼륨 삭제 시 볼륨 연결 종료 및 볼룸 회수 정책 준수. 
      1. 퍼시스턴트 볼륨 클레임 확인
      2. 컨트롤러가 퍼시스턴트 볼륨을 우선 검색
      3. 컨트롤러가 퍼시스턴트 볼륨 목록 중 요청 용량 이상, 선언 용량 최소의 퍼시스턴트 볼륨을 선택. 선택된 퍼시스턴트볼륨을 접근 모드로 나누고 용량 크기 오름차순 정렬 후 첫번째를 반환
    • 추가 자료 : 컨트롤러 소스 코드 확인 : https://github.com/kubernetes/kubernetes/tree/master/pkg/controller
  • 워커 노드 : 컨테이너를 직접 실행하는 작업을 수행.
    • Kubelet : 워커노드 실행 관련 전체를 담당. 컨테이너 라이브니스 프로브를 실행하여 프로브 실패 시 컨테이너 재시작 작업도 진행. API 서버 내 파드 삭제 시 컨테이너 정지 및 파드 종료 통보도 진행. 실행 과정에서 API 서버와 통신하지 않고, 파드 매니페스트를 로컬 디렉토리에 있는 파일을 사용하여 컨테이너를 실행시킬 수도 있음.

      1. 실행 중인 노드를 노드 리소스로 만들어 API 서버에 등록
      2. API 서버를 모니터링해 해당 노드에 파드가 스케줄링
      3. 스케줄링된 파드의 컨테이너를 시작 (컨테이너 런타임에 지정 이미지로 실행하도록 지시하는 작업)
      4. 실행 중인 컨테이너를 모니터링하며 상태를 API 서버에 보고

    • kube-proxy, 쿠버네티스 서비스 프록시 : API로 정의한 서비스에 클라이언트를 연결. 서비스의 IP, 포트로 유입된 접속을 서비스 지원 파드에 연결. 로드밸런싱도 수행. 

      프록시 이름 유래 : 클라이언트가 iptables 규칙을 정한 뒤 서비스 IP로 향하는 연결을 가로채 프록시 서버로 전송해 파드까지 이어지는 방식이 그 원형이었기 때문. 하지만, 현재는 iptables 규칙만 사용해 프록시 서버 없이 바로 패킷을 무작위 백엔드 파드로 전송하는 iptables 모드로 진행되기 때문에 요청을 보다 고르게 분산하고, 과거와 달리 사용자 공간이 아닌 커널 공간에서 패킷을 처리헤 성능을 향상시켰다. 

      서비스 관련 모든 사항은 각 노드 내 kube-proxy가 수행. 
      1. API 서버에 서비스를 생성
      2. 해당 서비스에 가상 IP 주소가 할당됨
      3. API 서버가 워커 노드 내 모든 kube-proxy 에이전트에 신규 서비스 생성 통보
      4. 각 kube-proxy가 실행 중인 노드에 해당 주소로 접근 가능하도록 함. (서비스의 IP, 포트로 향하는 패킷을 가로채 목적지 주소를 변경해, 패킷이 서비스를 지원하는 파드로 리디렉션되도록 하는 iptables 규칙을 설정해 진행)

      kube-proxy가 클라이언트를 서버에 연결하는 방법
      1. 클라이언트가 패킷을 서비스 IP 및 포트로 목적지를 지정해 전송
      2. 패킷이 네트워크로 전송되기 전 전송 출발지 노드의 커널이 iptables 규칙을 기반으로 패킷을 처리. 
      2-A. 커널이 iptables 규칙 중 해당 패킷이 일치하는 게 있는지 검사해, 규칙가 일치 시 목적지를 변경
      3. 변경된 목적지를 지닌 패킷은 클라이언트 파드가 서비스를 통하지 않고 다른 파드로 이동하는 것과 동일하게 전송됨.

      "노드 레벨에서 돌아가는 대표 리소스 2가지가 파드와 서비스다. 
        파드를 kubelet이 제어하고, 서비스는 프록시가 제어한다. 
        kube proxy가 iptables를 제어하고, iptables는 넷필터를 제어하여 리눅스 OS 레벨에서 서비스 로드밸런싱을 처리한다."

    • 컨테이너 런타임 (도커 등)
  • 애드온 구성 요소 : 보조 추가 구성 요소. YAML 매니페스트를 API 서버에 게시해 파드로 배포하는 방식으로 배포됨. 디플로이먼트 리소스, 레플리케이션 컨트롤러 리소스, 데몬셋 리소스 등의 형태로 배포. 
    • 쿠버네티스 DNS 서버 : 모든 파드는 내부 DNS 서버를 사용해 서비스 이름을 조회. 헤드리스 서비스 파드의 경우 해당 파드의 IP 주소를 조회. kube-dns 서비스로 노출하므로 클러스터 내 이동 가능. kube-dns 파드가 서버 감시 메커니즘을 통해 서비스, 엔드포인트 변화 관찰 후 DNS 레코드 갱신. 
    • 대시보드
    • 인그레스 컨트롤러 : 리버스 프록시 서버를 실행해 클러스터 정의 상의 인그레스, 서비스, 엔드포인트 등의 설정을 유지. 리소스 감시 중 변경 감지 시 프록시 서버 설정을 변경. 서비스의 파드로 직접 트래픽을 전달하는 특징 보유.
    • 힙스터 (추후 설명)
    • 컨테이너 네트워크 인터페이스 플러그인, CNI 플러그인

 

컨트롤러의 레플리카셋 생성 이벤트 체인

  1. kubectl 명령으로 디플로이먼트 매니페스트를 API 서버에 게시
  2. API 서버가 디플로이먼트 정의 검증
  3. API 서버가 ETCD에 디플로이먼트 매니페스스 저장 후 kubectl에 응답 전송
  4. API 서버를 감시하던 모든 API 서버 클라이언트가 새로운 디플로이먼트 리소스 생성에 대한 통보를 받음
  5. 통보를 받은 클라이언트 중 하나인 디플로이먼트 컨트롤러가 디플로이먼트 정의를 기반으로 레플리카셋을 생성
    1. 레플리카셋 생성 과정 (1) : 디플로이먼트 컨트롤러가 레플리카셋 정의를 API 서버에 올려 생성
      레플리카셋 생성 과정 (2) : 이를 감시하던 레플리카셋 컨트롤러가 통보를 받아 파드 정의를 API 서버에 올려 생성
      레플리카셋 생성 과정 (3) : 이를 감시하던 스케줄러가 통보를 받아 생성된 파드를 노드에 할당
    2. 파드 컨테이너 실행 과정 (1) : 스케줄러의 노드 할당을 확인. kubelet이 노드에 스케줄링된 새 파드를 확인.
      파드 컨테이너 실행 과정 (2) : kubelet이 파드 정의를 검사하고 컨테이너 런타임에 컨테이너 시작을 지시
      파드 컨테이너 실행 과정 (3) : 컨테이너 런타임이 컨테이너를 실행

 

파드 네트워킹

  • 네트워크는 쿠버네티스 자체가 아닌 시스템 관리자 / 컨테이너 네트워크 인터페이스 플러그인으로 제공.
  • 파드끼리 상호 통신이 가능하도록 해야 하고, 파드가 보는 자신의 IP와 다른 파드가 해당 파드를 보는 IP가 동일하도록 해야 함. 즉, 패킷이 네트워크 주소 변환(NAT) 없이 상호 다른 파드 간 통신의 출발지, 목적지 주소가 변경되지 않은 상태로 도착해야 함.

 

네트워킹 동작 순서

  1. 인프라스트럭처 컨테이너 시작 이전, 컨테이너용 가상 이더넷 인터페이스 쌍, veth 쌍이 생성됨. 
    1. 쌍의 인터페이스 중 하나는 호스트의 네임스페이스에 남음. (노드 내 veth 목록 중 하나) 컨테이너 런타임이 사용할 수 있도록 설정된 네트워크 브리지에 연결됨.
    2. 쌍의 인터페이스 중 다른 하나는, 컨테이너의 네트워크 인터페이스 안으로 옮겨져 eth0으로 이름이 변경됨. 네트워크 브리지의 주소 범위 내의 IP를 할당받음. 
  2. 컨테이너 내부에서 실행되는 애플리케이션이 eth0 인터페이스로 전송함
  3. 호스트 네임스페이스에 위치한 veth 인터페이스로 요청이 전달되어 브리지로 전송함
  4. 이를 통해 브리지에 연결된 모든 네트워크 인터페이스로 전송 가능. 
  5. 수신하는 파드 입장에서는 브리지에서 요청을 받아 해당 파드의 veth 쌍으로 받음

https://jeongjin984.github.io/posts/Kubernetes-network-1/

 

서로 다른 노드 간 브리지 연결 방법

  • 오버레이 네트워크
  • 언더레이 네트워크
  • 일반적인 계층 3 라우팅 : 노드 간 통신을 위해 노드의 물리 네트워크 인터페이스도 브리지에 연결해야 함. 라우터 없이 동일 네트워크 스위치에 연결된 경우에만 작동. 
    패킷을 다른 노드의 컨테이너로 보낼 때 패킷이 해당 파드의 veth를 거쳐 - 노드의 브리지를 거쳐 - 노드의 물리 어댑터(eth0)를 거쳐 - 회선을 통해 다른 노드의 물리어댑처를 거쳐 - 다른 노드의 브리지를 거쳐 - 다른 노드 해당 파드의 veth를 통과하는 방식으로 진행.

 

고가용성 클러스터

  • 가동 중단 시간 감소를 위한 다중 인스턴스 실행
  • 중단 시간 발생 방지를 위한 비활성 복제본 실행. 활성 상태인 인스턴스를 하나만 지정하도록 빠른 임대 ( fast-acting lease ), 리더 선출 메커니즘 등을 활용. 
  • 가용성 향성을 위해 etcd, API 서버, 컨트롤러 매니저, 스케줄러 등의 요소를 마스터 노드에서 실행
    • etcd는 분산 시스템이므로 고가용성을 위해 여러 노드에 분산 가능. (5, 7대)
    • 여러 API 서버 인스턴스 실행. API 서버는 스테이트리스하므로 분산 가능. 다만 로드밸런서가 API 서버 앞에 위치해 항상 정상 작동하는 인스턴스로만 연결됨. 그와 달리 etcd는 그 뒤에서 API 서버와 로컬로 통신.
    • 컨트롤러, 스케줄러의 경우 인스턴스 간 작업 중복 및 경쟁이 염려되기 때문에, 한번에 하나의 인스턴스만 활성화되도록 리더 선출 메커니즘 등을 활용해 리더의 작업 수행 중 다른 인스턴스는 작업 실패를 대기. 
  • 리더 선출 메커니즘 : 인스턴스 간 대화가 필요 없이 오브젝트 생성 작업만으로 동작. 엔드포인트 리소스 생성 및 갱신 시 leader라는 어노테이션에 먼저 해당 필드에 이름을 넣는데 성공한 인스턴스가 유일한 리더가 되는 구조. 
    • 낙관적 동시성 : 여러 인스턴스가 자신의 이름을 어노테이션에 기록하려고 노력해도 하나의 성공만 보장함. (위쪽 설명 참고)

 

[참고]

https://kubernetes.io/ko/docs/concepts/overview/components/

 

쿠버네티스 컴포넌트

쿠버네티스 클러스터는 컴퓨터 집합인 노드 컴포넌트와 컨트롤 플레인 컴포넌트로 구성된다.

kubernetes.io