제어 평면
고친 사람 github-actions[bot]
제어 평면은 시스템 안에서 무엇을 어떻게 할지 정해 일하는 쪽에 알려 줍니다. 일은 직접 하지 않고, 정한 대로 되고 있는지 지켜봅니다. 네트워크 장비에서는 데이터를 어느 길로 보낼지 정하는 부분을 이렇게 부릅니다. 컨테이너를 여러 기계에 나눠 띄우는 쿠버네티스에서는 기계 전체를 관리하는 프로그램 묶음을 가리킵니다.
쉽고 빠른 이해
제어 평면은 시스템 안에서 결정만 따로 맡는 쪽입니다. 쿠버네티스에 「웹 서버를 셋 띄워 둬라」라고 적으면, 어느 기계에 띄울지 고르고 셋이 계속 떠 있는지 지켜보는 것이 제어 평면입니다.
결정과 일을 한데 섞으면 일하는 기계마다 따로 판단해서 서로 엇갈립니다. 결정을 한곳에 모으면 전체를 보고 정할 수 있습니다. 일하는 쪽은 받은 지시를 적용하기만 하면 됩니다.
- 사람이 원하는 상태를 제어 평면에 적어 둡니다
- 제어 평면이 일하는 쪽의 지금 상태를 계속 살핍니다
- 둘이 다르면 일하는 쪽에 지시를 내려 차이를 메웁니다
대가는 제어 평면이 멈추면 새 결정도 멈춘다는 것입니다. 일하는 쪽은 받아 둔 지시대로 계속 돌지만, 죽은 서버를 다시 띄우는 일은 일어나지 않습니다. 이 위험을 줄이려고 제어 평면은 대개 여러 대로 나눠 돌립니다.
기계 한 대에서 도는 작은 서비스는 둘을 나눌 까닭이 적습니다. 일하는 기계가 여럿이면 제어 평면을 따로 세웁니다.
상세
결정하는 부분과 일하는 부분
공항에서 비행기는 승객을 태우고 하늘을 납니다. 관제탑은 아무도 태우지 않습니다. 대신 어느 활주로를 쓸지, 언제 뜨고 내릴지를 정해 비행기들에 알려 줍니다.
시스템도 이렇게 두 부분으로 나눠 볼 수 있습니다. 요청이나 데이터를 받아 처리하는 부분이 데이터 평면입니다. 그 처리에 쓸 규칙을 정해 내려보내는 부분이 제어 평면입니다. 영어로는 control plane 입니다. 실무에서는 소리 나는 대로 컨트롤 플레인이라고도 부릅니다.
「평면」은 한 시스템의 기능을 성격별로 겹쳐 놓은 층 하나를 뜻합니다. 같은 장비 안에서도 결정하는 기능과 일하는 기능을 서로 다른 층으로 떼어 본다는 말입니다.
백엔드 개발자가 가장 먼저 만나는 예는 클라우드의 관리형 서비스입니다. 콘솔 화면이나 관리용 API(Application Programming Interface, 프로그램이 다른 프로그램을 부르는 약속)로 데이터베이스를 만들고, 크기를 바꾸고, 지우는 호출이 제어 평면으로 갑니다. 만들어진 데이터베이스에 애플리케이션이 쿼리를 보내는 것은 데이터 평면입니다.
두 쪽은 따로 멈출 수 있습니다. 제어 평면이 고장 나 새 데이터베이스를 못 만들어도, 이미 돌고 있는 데이터베이스는 대개 쿼리를 계속 받습니다.
결정과 일을 떼어 놓는 이유
두 부분은 하는 일의 성격이 크게 다릅니다. 그 차이를 표로 먼저 봅니다. 이어서 떼어 놓으면 무엇이 좋아지는지 따라갑니다.
| 제어 평면 | 데이터 평면 | |
|---|---|---|
| 언제 도나 | 설정이나 상태가 바뀔 때 | 요청 하나마다 |
| 무엇을 보고 정하나 | 시스템 전체 | 자기 앞에 온 요청 하나 |
| 무엇이 중요한가 | 올바른 판단 | 요청을 늦추지 않는 처리 |
| 몇 개 있나 | 몇 대 | 일하는 기계 수만큼 |
데이터 평면은 요청 하나하나를 다룹니다. 조금만 느려져도 모든 요청이 늦어집니다. 데이터 평면에는 받은 규칙을 적용하기만 하는 단순한 일을 맡깁니다.
제어 평면은 전체를 봐야 판단할 수 있습니다. 어느 기계가 한가한지, 어느 길이 끊겼는지는 기계 하나 안에서는 안 보입니다. 결정을 한곳에 모으면 모든 기계가 같은 판단을 받습니다.
떼어 놓으면 장애도 섞이지 않습니다. 결정 쪽이 멈춰도 일하는 쪽은 마지막으로 받은 규칙으로 계속 돌아갑니다. 반대로 요청이 몰려 일하는 쪽이 바빠도 제어 평면은 설정 변경을 따로 받아 처리할 수 있습니다.
네트워크 장비에서 온 이름
이 이름은 라우터에서 먼저 쓰였습니다. 라우터는 네트워크와 네트워크 사이에 서서, 받은 데이터를 목적지 쪽으로 넘겨 주는 장비입니다.
네트워크에서 데이터는 작은 조각으로 나뉘어 오갑니다. 이 조각을 패킷이라고 부릅니다. 조각으로 나누면 여러 통신이 한 회선을 번갈아 나눠 쓸 수 있습니다.
라우터가 하는 일은 둘로 갈립니다. 첫째는 이웃 라우터들과 길 정보를 주고받아 「어느 목적지는 어느 쪽으로」라는 표를 만드는 일입니다. 이 표가 라우팅 테이블입니다.
길 정보를 주고받는 규칙은 라우팅 프로토콜이라고 부릅니다. 라우터들이 같은 규칙으로 말해야 서로의 길 정보를 알아듣습니다. 표를 만드는 이쪽이 제어 평면입니다.
둘째는 들어온 패킷의 목적지를 표에서 찾아 다음 장비로 내보내는 일입니다. 이 일을 포워딩이라고 부릅니다. 이쪽이 데이터 평면입니다. 포워딩은 패킷마다 거쳐야 하므로 큰 장비에서는 전용 하드웨어에 맡기기도 합니다.
flowchart TD
subgraph CP["제어 평면"]
RP["라우팅 프로토콜 · 이웃과 길 정보를 주고받는다"]
RT["라우팅 테이블"]
RP --> RT
end
subgraph DP["데이터 평면"]
IN["들어온 패킷"]
FW["포워딩 · 목적지를 표에서 찾는다"]
OUT["다음 장비"]
IN --> FW --> OUT
end
RT -->|찾아볼 표를 준다| FW
그림의 위쪽 묶음은 길이 바뀔 때만 돌아갑니다. 아래쪽 묶음은 패킷이 들어올 때마다 돌아갑니다.
나중에는 제어 평면을 장비 밖으로 꺼내는 방식도 나왔습니다. 장비마다 따로 길을 계산하지 않고, 가운데 서버 하나가 계산해 장비들에 내려보냅니다. 장비는 받은 표대로 넘기기만 합니다. 이 방식을 SDN(Software-Defined Networking, 소프트웨어 정의 네트워킹)이라고 부릅니다.
쿠버네티스의 제어 평면
같은 이름을 컨테이너 운영 도구도 가져다 씁니다. 그중 가장 널리 쓰이는 쿠버네티스를 예로, 제어 평면이 어떤 프로그램들로 이뤄지는지 봅니다.
컨테이너는 애플리케이션을 그것이 기대는 파일과 함께 묶어, 어느 기계에서나 같게 돌게 만든 실행 단위입니다. 한 번 묶어 두면 개발자 노트북에서도 서버에서도 똑같이 돌아갑니다.
서버 수십 대에 컨테이너 수백 개를 띄우고, 죽으면 다시 살리는 일을 사람이 손으로 하기는 어렵습니다. 이 일을 대신 맡는 것을 오케스트레이션이라고 합니다. 쿠버네티스가 이런 오케스트레이션 도구입니다.
쿠버네티스가 관리하는 기계 묶음을 클러스터라고 부릅니다. 사용자는 기계 하나하나에 따로 명령하지 않고 클러스터 전체에 일을 맡깁니다. 어느 기계에서 돌릴지는 쿠버네티스가 정합니다.
클러스터는 제어 평면과 여러 노드로 이뤄집니다. 노드는 컨테이너가 실제로 돌아가는 기계입니다. 노드를 늘리면 클러스터가 돌릴 수 있는 컨테이너 수도 늘어납니다.
쿠버네티스는 컨테이너를 하나씩 다루지 않고 파드라는 단위로 다룹니다. 파드 하나에는 컨테이너가 하나 이상 들어갑니다. 제어 평면이 노드에 배정하는 것도 파드입니다.
쿠버네티스의 제어 평면에서 기본이 되는 프로그램은 넷입니다.
| 구성 요소 | 하는 일 |
|---|---|
| API 서버(kube-apiserver) | 클러스터로 들어오는 모든 요청을 받는 입구입니다. 다른 구성 요소도 이것을 거쳐 정보를 주고받습니다 |
| etcd | 클러스터의 상태를 전부 적어 두는 키-값 저장소입니다. API 서버가 이것을 읽고 씁니다 |
| 스케줄러(kube-scheduler) | 아직 노드가 안 정해진 파드를 찾아 알맞은 노드에 배정합니다 |
| 컨트롤러 매니저(kube-controller-manager) | 컨트롤러들을 돌립니다. 컨트롤러는 사용자가 적어 둔 원하는 상태와 지금 상태를 견줘 차이를 메우는 프로그램입니다 |
넷 모두 컨테이너를 직접 띄우지 않습니다. 무엇을 어디에 띄울지를 정하고 적어 둘 뿐입니다.
띄우는 일은 노드 쪽이 합니다. 노드마다 kubelet이라는 프로그램이 돌아갑니다. kubelet은 API 서버에서 자기 노드에 배정된 파드를 받아 옵니다.
kubelet이 컨테이너를 손수 만들지는 않습니다. 컨테이너를 만들고 돌리는 일은 컨테이너 런타임이라는 프로그램이 맡습니다. kubelet은 런타임에 그 파드의 컨테이너를 띄우라고 시킵니다.
노드에는 kube-proxy도 돌아갑니다. 노드로 들어온 요청이 알맞은 파드에 닿도록 그 노드의 네트워크 규칙을 맞춰 둡니다. kubelet, 컨테이너 런타임, kube-proxy 가 쿠버네티스의 데이터 평면에 해당합니다.
flowchart TD
subgraph CP["제어 평면"]
API["API 서버"]
ETCD["etcd"]
SCH["스케줄러"]
CM["컨트롤러 매니저"]
SCH --> API
CM --> API
API --> ETCD
end
subgraph N1["노드 1"]
K1["kubelet"] --> P1["파드"]
end
subgraph N2["노드 2"]
K2["kubelet"] --> P2["파드"]
end
API <-->|배정된 파드 · 상태 보고| K1
API <-->|배정된 파드 · 상태 보고| K2
모든 선이 API 서버로 모입니다. 제어 평면 안에서도, 노드와 제어 평면 사이에서도 API 서버가 가운데 섭니다. kubelet은 자기 노드의 파드가 어떤지를 API 서버에 거꾸로 알립니다.
원하는 상태에 맞춰 가는 반복
파드 셋을 늘 띄워 두는 예로, 제어 평면이 결정을 어떻게 이어 가는지 따라갑니다.
사용자는 제어 평면에 「파드를 하나 띄워라, 또 하나 띄워라」라고 명령을 차례로 내리지 않습니다. 「이 파드가 셋 떠 있어야 한다」처럼 결과만 적어 둡니다. 이렇게 결과만 적고 방법은 맡기는 방식을 선언적 설정이라고 합니다.
spec:
replicas: 3 # 이 파드를 셋 유지
replicas 는 파드를 몇 개 띄워 둘지 적는 칸입니다. 사용자가 이렇게 적어 둔 바람을
원하는 상태라고 부릅니다. API 서버가 이것을 받아 etcd 에 저장합니다.
컨트롤러는 같은 일을 끝없이 되풀이합니다. 지금 떠 있는 파드를 세고, 원하는 수와 견주고, 모자라면 새로 만들고 남으면 지웁니다. 이 반복을 제어 루프라고 부릅니다. 조정 루프라고도 부릅니다.
flowchart TD
A["지금 떠 있는 파드를 센다"] --> B{"원하는 수와 같은가"}
B -->|같다| W["잠시 뒤 다시 센다"]
W --> A
B -->|다르다| C["모자라면 만들고 남으면 지운다"]
C --> A
노드 하나가 꺼져 그 위의 파드가 사라졌다고 해 봅시다. 떠 있는 파드가 둘로 줄면 컨트롤러가 차이를 봅니다. 컨트롤러가 새 파드를 하나 만듭니다. 스케줄러가 그 파드를 살아 있는 노드에 배정합니다. 그 노드의 kubelet이 파드를 띄우면 셋으로 돌아옵니다.
사람은 아무것도 하지 않았습니다. 원하는 상태를 한 번 적어 둔 것만으로, 제어 평면이 계속 그 모습에 맞춰 갑니다.
제어 평면이 멈추면
제어 평면이 멈춰도 노드 위의 파드는 곧바로 죽지 않습니다. kubelet이 이미 띄워 둔 컨테이너는 계속 돌아갑니다. 들어오는 요청도 계속 처리됩니다.
멈추는 것은 결정입니다. 새로 배포할 수 없습니다. 설정을 바꿔도 노드에 닿지 않습니다. 노드가 죽어 파드가 사라져도 다시 띄울 쪽이 없습니다.
라우터도 같습니다. 제어 평면이 멈추면 이미 만든 표로 패킷은 계속 넘기지만, 길이 바뀐 것은 따라가지 못합니다. 끊긴 길로 패킷을 계속 보낼 수 있습니다.
이런 멈춤을 막으려고 운영 환경에서는 제어 평면을 여러 기계에 나눠 돌립니다. 한 대가 죽어도 서비스가 이어지게 만드는 것을 고가용성이라고 합니다.
여러 대가 같은 내용을 가지려면 서로 값을 맞추는 절차가 필요합니다. 이 절차를 합의라고 합니다. 한 대만 값을 바꾸고 나머지가 모르면, 어느 대에 묻느냐에 따라 답이 달라지기 때문입니다.
etcd 는 합의 방식 가운데 Raft를 씁니다. Raft 는 전체의 과반이 살아 있어야 새 값을 받아들입니다.
etcd 는 대개 세 대나 다섯 대처럼 홀수로 둡니다. 세 대면 한 대가 죽어도 과반인 둘이 남습니다. 네 대로 늘려도 과반이 셋이라 견디는 수는 여전히 한 대입니다.
다른 분야에서 만나는 제어 평면
제어 평면은 라우터와 쿠버네티스 말고도 여러 분야에 나옵니다. 아래 표에 그 분야들을 나란히 놓았습니다.
프록시는 요청을 대신 받아 목적지로 넘겨 주는 중간 프로그램입니다. 요청이 프록시를 거치게 하면 서비스 코드를 고치지 않고도 통신 방식을 바꿀 수 있습니다.
서비스 메시는 서비스마다 옆에 프록시를 하나씩 붙여, 서비스끼리 주고받는 통신을 그 프록시들이 대신 나르게 하는 구조입니다. 프록시들이 따를 규칙은 한곳에서 정해 내려보냅니다.
서비스 옆에 붙어 그 서비스의 통신만 맡는 프록시를 사이드카라고 부릅니다.
| 분야 | 제어 평면이 하는 일 | 데이터 평면이 하는 일 |
|---|---|---|
| 라우터 | 길을 계산해 라우팅 테이블을 만듭니다 | 표를 보고 패킷을 넘깁니다 |
| SDN | 가운데 서버가 길을 정해 내려보냅니다 | 장비들이 받은 표대로 넘깁니다 |
| 쿠버네티스 | 파드를 어느 노드에 둘지 정하고 상태를 맞춥니다 | 노드가 컨테이너를 돌립니다 |
| 서비스 메시 | 규칙과 주소 목록을 프록시들에 내려보냅니다 | 사이드카가 요청을 나릅니다 |
| 클라우드 관리형 서비스 | 데이터베이스 같은 자원을 만들고 바꾸고 지웁니다 | 만든 자원이 요청을 처리합니다 |
어느 분야든 모양이 같습니다. 가운데서 전체를 보고 정하는 쪽이 하나 있습니다. 그 결정을 받아 요청 하나하나를 처리하는 쪽이 여럿 있습니다.
떼어 세울 때와 한데 둘 때
제어 평면은 설치하는 물건 하나가 아니라 시스템을 나눠 보는 방법입니다. 고를 것은 「쓸지 말지」가 아니라 「결정하는 쪽을 어디까지 따로 세울지」입니다.
기계 한 대에서 도는 작은 서비스는 둘을 나눌 까닭이 적습니다. 설정 파일을 읽는 일과 요청을 처리하는 일이 한 프로세스 안에서 끝납니다. 정할 거리가 설정 파일 하나뿐이기 때문입니다.
일하는 기계가 여럿으로 늘면 사정이 바뀝니다. 기계마다 설정을 손으로 맞추다 보면 어긋납니다. 어느 기계가 죽었는지 보고 다시 띄울 쪽도 필요해집니다. 이때 결정을 한곳에 모은 제어 평면을 따로 세웁니다.
따로 세운 제어 평면은 그 자체가 돌봐야 할 시스템입니다. 여러 대로 돌리고, 저장한 상태를 백업하고, 새 버전으로 올려야 합니다. 이 품을 덜려고 제어 평면만 클라우드 업체가 맡아 운영하는 관리형 쿠버네티스를 쓰기도 합니다. 그때 사용자는 노드만 다룹니다.
장애를 볼 때도 이 구분이 쓸모 있습니다. 배포나 설정 변경이 안 먹히면 제어 평면을 먼저 봅니다. 요청이 실패하거나 느리면 데이터 평면을 먼저 봅니다.
관련 항목
제어 평면과 짝을 이루는 평면
데이터 평면 · 관리 평면 · 사용자 평면
제어 평면이 결정을 이어 가는 방식
제어 루프 · 원하는 상태 · 선언적 설정 · 컨트롤러 · 스케줄링 · 자가 치유
쿠버네티스 제어 평면을 이루는 구성 요소
kube-apiserver · etcd · kube-scheduler · kube-controller-manager · cloud-controller-manager
제어 평면의 지시를 받아 일하는 노드 쪽 구성 요소
노드 · kubelet · 컨테이너 런타임 · kube-proxy · 파드 · 컨테이너
제어 평면이 길을 정하는 네트워크 장비와 프로토콜
라우터 · 라우팅 테이블 · 라우팅 프로토콜 · 포워딩 · SDN · OpenFlow · BGP · OSPF
제어 평면을 갖춘 시스템과 도구
오케스트레이션 · Kubernetes · 클러스터 · 서비스 메시 · Istio · 관리형 서비스 · 관리형 쿠버네티스
제어 평면의 장애를 견디는 기법
제어 평면이 속하는 상위 분류
소프트웨어 아키텍처 · 분산 시스템 · 네트워크 · 클라우드
다른 이름: control plane · 컨트롤 플레인 · 컨트롤플레인