쿠버네티스 배포판 — 직접 세운다 · k0s 구성과 동적 구성 · 이론
k0s 설정의 진실은 어디에 있나
한 줄 요약
k0s 의 클러스터 설정은 정적 구성이면 각 컨트롤러의 파일, 동적 구성이면 API 안의 ClusterConfig 오브젝트에 있습니다. 어느 모드인지 모르고 파일을 고치면 "고쳤는데 아무 일도 없다" 가 됩니다.
왜 이게 필요했나
k0s 는 바이너리 하나와 k0s.yaml 하나로 클러스터를 세운다고 소개됩니다. 그래서 운영자는 설정을 바꿀 때 자연스럽게 /etc/k0s/k0s.yaml 을 엽니다. 여기서 두 가지 사고가 납니다.
첫째, 기본인 정적 구성에서는 k0s 가 파일을 감시하지 않습니다. [구성 문서](https://docs.k0sproject.io/stable/configuration/)는 실행 중에 파일을 바꿀 수는 있지만 반영하려면 k0s 를 재시작하라고 적습니다. 컨트롤러가 여러 대라면 모든 컨트롤러의 파일을 똑같이 맞추고 전부 재시작해야 합니다. 한 대만 고치고 잊으면, 다음에 그 노드가 재시작될 때 비로소 설정이 바뀌고 컨트롤러끼리 서로 다른 설정을 들고 있게 됩니다.
둘째, 이 불편을 없애려고 동적 구성(--enable-dynamic-config)을 켜면 반대 방향의 함정이 생깁니다. [동적 구성 문서](https://docs.k0sproject.io/stable/dynamic-configuration/)에 따르면 클러스터를 처음 만들 때 첫 컨트롤러가 파일을 부트스트랩 값으로 한 번 읽어 API 에 저장하고, 그 뒤로는 API 의 오브젝트가 모든 컨트롤러의 진실의 원천입니다. 이제 파일의 클러스터 수준 설정은 고치고 재시작해도 무시됩니다.
어떻게 동작하나
설정 파일은 부분만 써도 된다
k0s config create 는 기본값이 모두 채워진 설정을 출력합니다. 하지만 k0s 는 부분 설정을 받아 빠진 값은 기본값으로 채우므로, 실제 파일에는 바꾸고 싶은 것만 두는 편이 읽기 좋습니다. 기본값을 보는 이유는 "내가 적지 않은 값이 무엇으로 정해지는가" 를 알기 위해서입니다. 이 실습의 VM 에서 기본 출력은 파드 대역 10.244.0.0/16, 서비스 대역 10.96.0.0/12, 저장소 etcd, telemetry 켜짐이었습니다. 이 VM 은 쿠버네티스 위에서 돌기 때문에 기본 대역이 호스트 클러스터와 겹쳐 파일에서 172.20/172.21 로 바꿔 두었습니다.
k0s sysinfo 는 같은 바이너리가 설치 전에 돌리는 사전 점검입니다. 메모리, /var/lib/k0s 파일시스템과 여유 공간, cgroup 판과 컨트롤러, 커널 설정을 항목마다 pass·warning 으로 보여 주고 -o json 으로 기계가 읽을 수도 있습니다.
클러스터 설정과 노드 설정은 다르다
동적 구성에서도 파일이 전부 무시되지는 않습니다. 문서는 spec.api(그 노드의 API 서버), spec.storage(그 노드의 etcd 또는 SQLite), spec.network.controlPlaneLoadBalancing 을 노드별 설정으로 분류해 계속 파일에서 읽는다고 합니다. API 서버와 저장소가 떠야 오브젝트를 읽을 수 있으니 당연한 설계입니다. 또 network.podCIDR, serviceCIDR, provider 등은 클러스터를 만든 뒤 바꿀 수 없는 항목이라, 수동 설치 때 파일에 반드시 적어야 한다고 합니다.
그 밖의 클러스터 수준 설정(워커 프로필, kube-router·kube-proxy 설정, 헬름 확장 등)은 kube-system 네임스페이스의 clusterconfig/k0s 오브젝트가 결정합니다. 컨트롤러는 오퍼레이터 방식으로 이 오브젝트를 지켜보다가 바뀌면 관련 리소스를 다시 만들고, 결과를 이벤트로 남깁니다. k0s config status 가 그 이벤트(SuccessfulReconcile, FailedReconciling)를 보여 줍니다.
정적 구성 파일 수정 ──(재시작해야)──▶ 반영동적 구성 파일 수정 ──(재시작해도)──▶ 클러스터 수준 설정은 무시 ClusterConfig 수정 ──(즉시)──▶ 반영, 이벤트 기록워커 프로필은 ConfigMap 으로 나타난다
[워커 노드 구성 문서](https://docs.k0sproject.io/stable/worker-node-config/)의 워커 프로필(spec.workerProfiles)은 kubelet 설정 덮어쓰기 묶음입니다. k0s 는 프로필마다 ConfigMap 을 만들고, 워커는 --profile 로 고른 ConfigMap 을 시작할 때 읽습니다. 실측으로는 이름이 worker-config-<프로필>-1.36 처럼 쿠버네티스 마이너 판이 붙었습니다. 그래서 설정이 반영됐는지를 이 ConfigMap 의 존재와 kubeletConfiguration 안의 값으로 눈에 보이게 확인할 수 있습니다.
헬름 확장은 Chart 오브젝트를 만든다
[헬름 차트 문서](https://docs.k0sproject.io/stable/helm-charts/)는 두 방법을 설명합니다. Chart 오브젝트를 직접 만드는 방법(권장)과, spec.extensions.helm 에 저장소와 차트를 선언하면 k0s 가 Chart 오브젝트로 바꿔 주는 방법입니다. 저장소는 index.yaml 이 있는 정식 헬름 저장소여야 하고, Chart 가 지워지면 k0s 가 릴리스를 제거합니다. 실측에서 선언으로 만든 Chart 이름은 k0s-addon-chart-<차트 이름> 이었고, 제거용 finalizer 가 붙어 있었습니다.
현장에서 만나는 모습
실측으로 본 장면을 옮깁니다(k0s v1.36.4+k0s.0).
- 정적 구성에서 파일에 워커 프로필을 넣고 45초를 기다려도 ConfigMap 은 생기지 않았고,
k0s stop·k0s start뒤 17초 만에 생겼습니다. - 이미 설치된 컨트롤러에
k0s install controller --enable-dynamic-config를 다시 치면 "Init already exists" 로 거절됐고--force가 필요했습니다. - 동적 구성으로 바꾼 뒤 만들어진 ClusterConfig 는 파일의 워커 프로필을 담고 있었지만,
spec.network.serviceCIDR가 10.96.0.0/12 로 보였습니다. 실제 API 서버의--service-cluster-ip-range와 kube-dns 주소는 파일대로 172.21 이었습니다. 바꿀 수 없는 항목은 오브젝트에 보이는 값을 믿지 말고 실제 구성요소에서 확인해야 합니다. - 동적 구성에서 파일에 프로필을 더하고 재시작까지 했지만 오브젝트의
generation은 그대로였고 ConfigMap 도 없었습니다. 오브젝트를 patch 하자 1초 안에 생겼습니다. - 오브젝트의
workerProfiles를 merge patch 로 새 목록만 담아 보내자 목록이 통째로 바뀌어, 빠진 프로필의 ConfigMap 이 곧바로 지워졌습니다. 목록에 더할 때는 JSON patch 의 add 를 씁니다. - 선언은 그대로 둔 채 Chart 오브젝트만
kubectl delete하자 릴리스가 바로 지워졌는데, 99초를 기다려도, ClusterConfig 를 한 번 더 고쳐도 되살아나지 않았고 k0s 를 재시작한 뒤에야 다시 생겼습니다. 선언과 실제가 조용히 어긋날 수 있다는 뜻입니다.
실무에서 진짜 중요한 것
- 먼저 모드를 확인합니다. 서비스 유닛의 실행 줄에
--enable-dynamic-config가 있는지 보는 것이 가장 빠릅니다. 모든 컨트롤러가 같은 모드여야 하고, 섞이면 충돌한다고 문서가 경고합니다. - 정적 구성이면 모든 컨트롤러의 파일을 같게 맞추고 재시작까지가 한 작업입니다.
- 동적 구성이면 클러스터 수준 변경은 오브젝트로 하고
k0s config status로 결과를 확인합니다. 파일에는 노드별 설정과 바꿀 수 없는 네트워크 값만 의미가 있습니다. - 반영 여부는 결과물로 확인합니다. 명령이 성공했다는 것보다 ConfigMap·Chart·릴리스가 실제로 생기거나 사라졌는지를 봅니다.
- 헬름 확장을 지울 때는 선언을 지웁니다. Chart 만 지우면 다음 재시작에 되돌아옵니다. 반대로 선언을 지우면 릴리스가 제거되므로, 데이터가 있는 차트라면 그 결과를 먼저 생각해야 합니다. 네임스페이스는 남았습니다.
다음 실습에서 할 것
정적 구성으로 뜬 k0s 한 대에서 시작합니다. 기본값과 파일을 비교하고 사전 점검을 읽은 뒤, 파일에 워커 프로필을 넣어 재시작 전후를 비교합니다. 동적 구성으로 바꿔 ClusterConfig 가 생기는 것을 보고, 같은 방식으로 파일을 고치면 이번에는 재시작해도 무시되는 것을 확인합니다. 마지막으로 오브젝트에 워커 프로필과 podinfo 차트를 선언하고, 선언을 지웠을 때 무엇이 남는지 기록합니다.