Managing Kubernetes의 번역서로 원서는 2018년 12월에 나왔고 번역서는 2019년 6월에 나왔다.
Kubernetes 1.13이 2018년 4월에 나왔고 1.15가 2019년 6월에 나왓으니 책에서 Kubernetes 버전을 명확히 얘기하진 않지만 1.12, 1.13 정도에 있는 걸로 보인다. 벌써 4년 전이고 얼마 전에 Kubernetes 1.27이 릴리스 되었으니 현재 버전과는 꽤 차이가 있다고 할 수 있다.
Kubernetes 운영이 주 업무는 아니지만 트러블슈팅을 해야 할 때 어려움을 겪는 경우가 있어서 Kubernetes에 관해 더 알기 위해 이 책을 골랐고 책을 읽을 때 4년 전 책이라는 것까지는 몰랐지만 "창시자가 알려주는 쿠버네티스 클러스터 현장 운영 기법" 부제에 끌려서 책을 읽기 시작했다.
결론부터 말하자면 Kubernetes 내부에 대해서 알려주길 바랐는데 기대에 비해서 아주 아쉽다. Kubernetes 입문 책은 아니기 때문에 Kubernetes API나 스케쥴러, 어드미션 컨트롤러 등 내부 컴포넌트와 구조에 관해서 설명해 주기는 하지만 그 깊이가 너무 얕은 느낌이다. 각 컴포넌트를 언급하고 설명해 주긴 하는데 그 이상이 궁금한데 소개만 해주는 느낌이라서 내 궁금함이 많이 채워지지 못했다. 결국 이 부분 이상을 보려면 Programming Kubernetes 뿐인가 하는 생각도 들었다.(물론 Programming Kubernetes는 읽을 당시에는 잘 이해하지 못했다.)
그래도 보통 Kubernetes의 사용 방법을 다루는 책이 많은데 이 책은 내부 컴포넌트와 동작에 관해서 설명하고 있기 때문에 이런 부분의 궁금함이 있다면 정리하는 데 도움이 될 책이라고 생각한다. 알고 있는데 어설프게 알면서 정리된 것도 있고 잘 모르고 있다고 책에서 알게 된 것들도 있다. 아래는 그중에서 인상적인 부분만 몇 개 기록으로 남겨둔다.
쿠버네티스는 대안적인 분산 방식을 취합니다. 단일 컨트롤러 대신 쿠버네티스는 많은 수의 컨트롤러로 구성되어 있으며 각 컨트롤러는 자체 독립 조정 루프를 수행합니다. 각각의 개별 루프는 시스템의 작은 부분(예: 특정 로드 밸런서의 엔드포인트 목록 업데이트)만 책임지며, 각 소형 컨트롤러는 다른 나머지 부분과는 완전히 무관합니다. 작은 문제에 집중하고 해결하면 전체 시스템을 훨씬 더 안정되게 만듭니다.
쿠버네티스 개발에서는 다음과 같은 기본적인 설계 원칙이 중요합니다.
첫 번째 원칙은 쿠버네티스가 일반적인 유닉스 철학인 '각자 일을 잘 수행하는' 작은 조각, 즉 모듈화를 기반을 한다는 것입니다. 쿠버네티스는 단일 바이너리에서 다양한 기능을 모두 구현하는 단일 애플리케이션이 아닙니다. 대신 쿠버네티스라는 전체 시스템을 구현하기 위해 서로 모르는 다른 애플리케이션의 모음입니다.
다시 말하지만, 이 모듈 방식의 장점은 쿠버네티스가 유연하다는 것입니다. 시스템의 큰 부분을 교체할 때도 다른 부분을 고려하거나 신경 쓰지 않고 그 조각만 교체할 수 있습니다. 물론 단점이라면 시스템을 배포하고 모니터링하고 이해하기 위해서 여러 가지 툴에 정보와 구성을 통합해야 하므로 복잡하다는 것입니다.
쿠버네티스의 두 번째 구조 설계 원칙은 구성 요소 간의 모든 상호작용이 중앙 집중식 API로 이루어지는 것입니다. 이 설계의 중요한 점은 구성 요소가 사용하는 API가 다른 모든 클러스터 사용자가 사용하는 API와 완전히 동일하다는 것입니다. 이는 쿠버네티스에 두 가지 중요한 결과를 가져옵니다. 첫 번째는 시스템의 어느 부분도 다른 어느 부분보다 특권을 갖지 않고 상호 간에 직접 접근할 수 없다는 것입니다. 실제로 API를 구현하는 API 서버를 제외하고는 내부에 대한 접근 권한이 없습니다.
API 기반 상호작용은 버전 왜곡이 존재할 때도 시스템이 안정적으로 설계되도록 합니다. 분산 시스템을 장비 관점의 그룹으로 롤아웃하면 일정 기간 소프트웨어의 이전 버전과 새 버전을 동시에 실행하게 됩니다.
API 서버에 대한 모든 요청은 HTTP 주소로 시작하는 RESTful API 패턴을 따릅니다. 모든 쿠버네티스 요청은 접두사
/api/
(핵심 API) 또는/apis/
(API 그룹별로 그룹화된 API)로 시작합니다. 두 가지 경로 집합은 기본적으로 각자 중요한 이유가 있습니다. API 그룹은 원래 쿠버네티스 API에는 없었기 때문에 파드나 Service와 같은 오리지널 혹은 코어 오브젝트 API 그룹이 없는/api/
접두어로 유지됩니다. 후속 API는 일반적으로 API 그룹 아래에 추가되므로/apis/<api-group>/
경로를 따릅니다.
logs는 본래 스트리밍 요청이 아닙니다. 클라이언트는 특정 파트(예:
/api/v1/namespaces/default/pods/some-pod/logs
)의 경로 끝부분에/logs
를 추가하여 파드 로그를 가져오도록 요청한 다음, 컨테이너 이름을 HTTP 쿼리 파라미터와 HTTP GET 요청으로 지정합니다. 기본 요청이 있을 때, API 서버는 현재 시간까지의 모든 로그를 일반 텍스트로 반환한 다음 HTTP 요청을 닫습니다. 그러나 클라이언트가 follow 쿼리 파라미터를 지정하여 로그를 추적하도록 요청하면 API 서버가 HTTP 응답을 계속 열어두고, 새 로그를 API 서버를 통해 쿠블렛에서 수신할 때 HTTP 응답에 기록합니다.
API 서버가 지원하는 추가 고급 작업은 쿠버네티스 API의 낙관적 동시(optimistically concurrent) 업데이트를 수행하는 기능입니다.
많은 API 서버 클라이언트의 작동에는 다음과 같은 3가지 작업이 포함됩니다.
1. API 서버에서 일부 데이터를 읽습니다.
2. 해당 데이터를 메모리에 업데이트합니다.
3. API 서버에 다시 씁니다.
스케줄러의 기본 작동은 다음과 같습니다. 첫째, 스케줄러는 현재 알려진 노드와 정상 노드의 모든 목록을 가져옵니다. 그런 다음 각 사전 조건에 대해서 노드와 스케줄된 파드를 기준으로 스케줄러가 사전 조건을 평가합니다. 노드가 실행 가능하면(파드가 해당 노드에서 실행될 수 있음), 노드가 스케줄링 가능한 노드 목록에 추가됩니다. 다음으로 모든 우선순위 기능이 파드와 노드의 조합으로 작동됩니다. 결과는 점수순으로 정렬된 우선순위 대기열에 입력되며 대기열 맨 위에 가장 점수가 높은 노드가 있습니다. 그런 다음 동일한 점수를 가진 모든 노드가 우선순위 대기열에서 빠져나와서 최종 목록에 배치됩니다. 이 노드들은 완전히 동일한 것으로 간주되며 그중 하나는 라운드 로빈 방식으로 선택되고 파드가 스케줄 되어야 하는 노드로 반환됩니다. 라운드 로빈은 무작위로 선택하는 대신 동일한 노드 사이에서 파드의 분포가 균등해지도록 사용됩니다.
파드가 스케쥴링된 시간(T_1)과 컨테이너가 실제로 실행되는 시간(T_N) 사이에 지연 시간이 있기 때문에, 스케줄링과 실행 사이의 간격 동안 다른 작업 때문에 스케줄링 결정이 무효가 될 수 있습니다.
충돌은 그다지 중요하지 않으며 결국엔 정상화됩니다. 따라서 이러한 충돌은 쿠버네티스가 무시합니다. 스케줄링 결정은 단 한 순간에만 최적입니다. 시간이 지나고 클러스터가 변경되면 항상 더 나빠질 수 있습니다.
Comments