kube-apiserver 를 컨트롤 플레인 외부에 설치해서 사용하기
Kube-apiserver 를 컨트롤플레인 외부에 설치해서 사용할 수 도 있다는 내용을 openai 의 기술블로그를 통해 영감을 얻고 직접 테스트해본 글입니다. Dedicated Node(전용 노드) 에서 kube-apiserver 를 사용하는 상황에서 유용하게 쓰일 방법입니다. 프로덕션에서 사용하기에는 적절하지 않은 테스트이지만 그럼에도 PoC (개념검증) 수준에서 실제 코드, 설정, 인증서를 수정해가면서 직접 테스트 가능하도록 작성한 글입니다.


개요
원래 kube-apiserver 는 보통 컨트롤 플레인 노드 안에서 static pod
형태로 운영된다.
static pod
는 kubernetes
가 관리하진 않지만(생성/추가/삭제 등), kubernetes
에서 관측 가능한 파드이고, 이것은 /etc/kubernetes/manifests
에 정의된 yaml
들을 파드로 kubelet
이 바라보고 생성하는 컨테이너들이다.
엄밀히 말하자면 쿠버네티스의 자원은 아니지만, 쿠버네티스가 써야 할 컴포넌트들을 주로 static pod
로 생성해서 사용한다. 대표적인 예가 kube-apiserver
, kube-scheduler
, kube-controller-manager
등이다. 만약 kubeadm
으로 별도의 설정 없이 클러스터를 프로비저닝하면 etcd
또한 static pod
로 프로비저닝 하게 된다.
Dedicated Node
dedicated node
란 보통 특정 서비스/애플리케이션/워크로드의 성능 극대화를 위해, 단일 서비스/애플리케이션/워크로드만을 하나의 노드(머신)에서 운영하는 케이스를 의미한다. 보통 I/O 작업이 많거나, 아니면 다른 워크로드에 의해서 영향을 받으면 안되는, 혹은 크리티컬한 워크로드를 이런형태로 사용 가능하고 그 대표적인 예가 etcd
이다. etcd
의 각종 사용사례를 보면 궁극적으로 성능 최대화, 클러스터의 안정성을 위해서 별도의 머신에서 etcd
만을 두고 운영하는 형태를 권장한다. 특히 클러스터 내 노드의 개수가 많은경우가 그렇다.
openai
의 경우는 kube-apiserver
또한 dedicated node
에서 static pod 가 아닌 별도의 systemd service
로 사용하는것으로 추측된다. 그러한 사용사례가 크게 문제가 될 것이 없는것이
첫째, kube-apiserver
는 stateless
하다. 단지 뒷단의 백엔드에 etcd
가 있긴 하지만 kube-apiserver
자체가 상태를 들고있고, 상태에 의해서 문제가 발생하는 서비스가 아니다.
두번째, kube-apiserver
는 etcd
와만 잘 연결되면 외부에서 kube-apiserver
에 request 를 했을 때, 그것을 처리하는데 문제가 없다.
위와 같은 특성으로 kube-apiserver
는 꼭 컨트롤플레인 노드에서 실행되어야 하는것이 아니다!
실제 테스트
여기서 kube-apiserver
는 systemd
서비스와 같이 컨테이너가 아닌 네이티브한 프로세스
로 실행하는것으로 테스트해보았다. 이 테스트에서의 전제조건은 etcd
는 stacked 가 아닌 external
형태로 배포되어있을 때의 환경에서 테스트 하였다.
환경
node1
: 컨트롤플레인, 192.168.201.12
/ etcd 가 systemd
로 실행중
node2
: 컨트롤플레인, 192.168.201.13
/ etcd 가 systemd
로 실행중
node3
: etcd 가 systemd
로 실행중
node4
: 아무것도 없는 노드 192.168.201.15
목표 구성
node1
에서 static pod
로 실행중인 kube-apiserver
를 node4
에서 프로세스로 띄워보고, node4
의 kube-apiserver
에 요청을 했을 때 잘 처리되는지 확인한다.
컨트롤 플레인에서 kube-apiserver
static pod
제거
우선 kubelet은 /etc/kubernetes/manifests
에 있는 yaml
파일을 static pod
로 띄우게 된다.
[root@node1 manifests]# pwd
/etc/kubernetes/manifests
[root@node1 manifests]# tree .
.
├── kube-apiserver.yaml
├── kube-controller-manager.yaml
└── kube-scheduler.yaml
위와 같이 총 3개의 파드가 static pod
로 띄워져있는데, static pod
를 제거하는 방법은, 단순하게 해당 디렉터리에서 yaml
을 제거하는것이다. 따라서 mv kube-apiserver.yaml ~/
을 통해 파일을 삭제하진 않고 제거해보았다.
이후 ps -ef | grep kube
를 통해 kube-apiserver
프로세스(컨테이너) 가 있는지 확인해본다.

이후, kubeadm 으로 생성되었던 apiserver
인증서에 node4
의 ip
주소를 SAN (subject alternative name) 으로 넣어줘야 하기 때문에 /etc/kubernetes/kubeadm-config.yaml
파일을 수정한다.

이후에 /etc/kuberentes/pki
, /etc/kubernetes/ssl
(이건 kubespray 썼을 경우 인증서 경로) 내의 apiserver.crt
, apiserver.key
를 삭제한다.
이후
$ kubeadm init phase certs apiserver --config=/etc/kubernetes/kubeadm-config.yaml
을 통해 apiserver
인증서를 새로 생성한다.
apiserver.crt
, apiserver.key
인증서를 node4
머신에 옮겨주면 되는데, 테스트 차원에서 sa.pub / sa.key 같은 인증서들도 필요하기때문에 통째로 node4 머신에 옮겨준다.
$ scp /etc/kubernetes/ssl/* hayden@192.168.201.15:/home/hayden
이후 etcd
인증서 또한 옮겨주어야 한다. kubespray
를 사용해 etcd
를 프로비저닝 한 케이스에서 etcd
인증서는 /etc/ssl/etcd/ssl
경로에 들어가있다. 여기서 원칙적으로는 node4
가 사용할 별도 인증서를 넘겨주는게 맞겠지만, 테스트 차원에서 /etc/ssl/etcd/ssl
경로에 있는 ca.pem
, node-node2.pem
, node-node2-key.pem
인증서를 node4
머신에 옮겨준다.
$ scp /etc/ssl/etcd/ssl/{ca.pem,node-node2.pem,node-node2-key.pem} hayden@192.168.201.15:/home/hayden
이후 node4
머신에서 kube-apiserver
바이너리를 다운로드 받는다.

Download Kubernetes 해당 링크에서 찾을 수 있다.
$ wget https://dl.k8s.io/v1.29.1/bin/linux/arm64/kube-apiserver
(테스트중인 환경의 아키텍처에 맞게 주의해서 다운로드)
이후 node4
머신에서 kube-apiserver
를 인자와 함께 아래와 같이 실행한다. (각자 상황에 따라 ip 주소 등이 다를 수 있음 주의)
$ ./kube-apiserver --advertise-address=192.168.201.15 --allow-privileged=true \
--anonymous-auth=True --apiserver-count=2 --authorization-mode=Node,RBAC --bind-address=0.0.0.0 \
--client-ca-file=./ca.crt --default-not-ready-toleration-seconds=300 --default-unreachable-toleration-seconds=300 \
--enable-admission-plugins=NodeRestriction --enable-aggregator-routing=False --enable-bootstrap-token-auth=true \
--endpoint-reconciler-type=lease --etcd-cafile=./ca.pem --etcd-certfile=./node-node2.pem \
--etcd-compaction-interval=5m0s --etcd-keyfile=./node-node2-key.pem \
--etcd-servers=https://192.168.201.12:2379,https://192.168.201.13:2379,https://192.168.201.14:2379 \
--kubelet-client-certificate=./apiserver-kubelet-client.crt --kubelet-client-key=./apiserver-kubelet-client.key \
--kubelet-preferred-address-types=InternalDNS,InternalIP,Hostname,ExternalDNS,ExternalIP --profiling=False \
--proxy-client-cert-file=./front-proxy-client.crt --proxy-client-key-file=./front-proxy-client.key --secure-port=6443 \
--requestheader-username-headers=X-Remote-User --requestheader-group-headers=X-Remote-Group \
--requestheader-extra-headers-prefix=X-Remote-Extra- --service-account-issuer=https://kubernetes.default.svc.cluster.local \
--service-account-key-file=./sa.pub --service-account-lookup=True --service-account-signing-key-file=./sa.key \
--storage-backend=etcd3 --tls-cert-file=./apiserver.crt --tls-private-key-file=./apiserver.key
kube-apiserver
는 정말 많은 인자/옵션들이 있는데 이것을 직접 알아내서 확인한것이 아니라, 아까 node1
에서 본 kube-apiserver.yml
static pod
명세에서 찾아서 넣으면 된다. 여기서 수정해야 할 값들은
--client-ca-file
, --etcd-certifle
과 같은 인증서 옵션들이다. (나중에 더 자세히 작성)
이렇게하고 node4
에서 kube-apsierver
를 실행하면 kube-apiserver
가 동작하기 시작한다.
이것을 테스트하기 위해 다시 node1
으로 넘어가서 kubectl
이 사용할 config
파일을 수정해본다.
$ vim /root/.kube/config

node1
이 자기자신 static pod
가 당연하게도 localhost로 뜨기때문에, localhost:6443 으로 호출하도록 되어있었다. 이것을 node4 주소로 변경이렇게 하게 되면 앞으로 node1
머신에서의 kubectl
호출은 node4
(192.168.201.15) 에 있는 kube-apiserver
에 호출을 하게 된다.
$ curl -k "https://192.168.201.15:6443/readyz?verbose"
위와 같이 node4
에 떠있는 kube-apiserver
의 상태를 조회해볼 수 있고.

$ kubectl get --raw='/readyz?verbose'
을 통해 kubectl
로 동일한 결과를 확인 할 수 있다.

$ kubectl get po -A
와 같은 일반적인 요청도 잘 처리 된다.

위는 node1
, 아래는 node4
이다. 이렇게 컨트롤 플레인 외부에 kube-apiserver
를 둘 수 있다 ! (node4
는 쿠버네티스와 전혀 관계 없는 머신이다)
Wrapping up
이렇게 K8s 의 Control Plane 외부에서 kube-apiserver
를 실행하고 정상 동작하는것을 확인해봤는데 당연히 프로덕션에서 이러한 구조를 사용하기 위해서는 kube-apiserver
를 systemd
서비스로 래핑해서 사용하거나 혹은 kubelet
+ static pods
조합으로 사용해야 할 것이다.
만약 kube-apiserver
를 이러한 dedicated node
에서 systemd
서비스등으로 설치해서 사용하는 경우 쿠버네티스 클러스터의 자체적인 로깅/모니터링/관리는 포기하게 되므로 트레이드 오프를 잘 판단해서 사용해야 한다.
OpenAI의 경우 거대한 규모의 K8s 클러스터를 운영하고있고, 그에 따른 kube-apiserver
의 부하도 엄청난 상황에서 kube-apiserver
를 위한 전용(dedicated)노드에서 실행하여 조금 더 안정적으로 쿠버네티스 클러스터를 운영하려고 했던 케이스로 보인다.
일반적인 유즈케이스에서는 이렇게 사용할 필요는 없겠지만 K8s Control Plane 의 일부 컴포넌트를 이런식으로 인증서 설정만 잘 하면 클러스터 외부에 설치해서 사용 할 수 있다는 아이디어를 얻을 수 있는 계기가 되었다.