쿠버네티스 배포판 — 직접 세운다 · RKE2 와 CIS 프로필 · 이론
RKE2 — 기본값이 보안 쪽으로 기운 배포판
한 줄 요약
RKE2 는 k3s 처럼 바이너리 하나로 세우지만 저장소는 처음부터 etcd 이고, profile: cis 한 줄로 restricted 파드 보안과 etcd 전용 사용자 같은 하드닝을 켜는 배포판입니다. 그 한 줄은 호스트가 준비돼 있을 때만 켜집니다.
왜 이게 필요했나
k3s 는 엣지와 개발 환경을 위해 가볍게 만든 배포판이라, 저장소 기본값이 sqlite 이고 부품도 편한 쪽으로 들어옵니다. 그런데 공공기관이나 금융처럼 CIS 벤치마크 점검표를 들고 오는 고객 앞에서는 "편한 기본값" 이 오히려 감사 항목이 됩니다. 점검할 때마다 API 서버 플래그를 하나씩 고치고, etcd 디렉터리 권한을 맞추고, 네임스페이스마다 파드 보안 라벨을 붙이는 일을 사람이 반복하면 반드시 한 곳이 빠집니다.
RKE2 는 그 반복을 배포판 안으로 옮긴 것입니다. [RKE2 CIS 하드닝 가이드](https://docs.rke2.io/security/hardening_guide)의 설명대로, profile 을 켜면 RKE2 가 PSA 설정 파일과 기본 네임스페이스의 NetworkPolicy 를 직접 써 넣고, etcd 를 etcd 사용자로 돌리며, 파일 권한을 좁힙니다. 대신 호스트 쪽 준비(커널 값, etcd 사용자)는 운영자 몫으로 남기고, 시작할 때 그 준비가 됐는지 검사합니다. 설계가 "알아서 고쳐 주기" 가 아니라 "안 되어 있으면 멈추기" 인 이유는, 호스트 설정을 배포판이 몰래 바꾸면 그 호스트의 다른 소프트웨어를 깨뜨릴 수 있기 때문입니다.
어떻게 동작하나
먼저 설치 모양이 k3s 와 다릅니다. [빠른 시작 문서](https://docs.rke2.io/install/quickstart)대로 kubeconfig 는 /etc/rancher/rke2/rke2.yaml, kubectl·crictl·ctr 은 /var/lib/rancher/rke2/bin 에 있고 기본 PATH 에 들어 있지 않습니다. 컨트롤 플레인은 k3s 처럼 한 프로세스에 묶이지 않고 kubelet 이 띄우는 정적 파드(etcd, kube-apiserver 등)입니다.
이 VM 에서 기본 프로필로 세운 결과(실측, v1.36.4+rke2r1):
cni canal (문서의 기본값)ingress class traefik (v1.36 부터 새 클러스터의 기본, 문서)datastore etcd 정적 파드storage class 없음 — k3s 의 local-path 같은 것이 들어오지 않는다대역 파드 10.42.0.0/16 · 서비스 10.43.0.0/16 · DNS 10.43.0.10PSA 설정 /etc/rancher/rke2/rke2-pss.yaml, enforce: privilegedetcd 프로세스 root대역은 [서버 설정 참조](https://docs.rke2.io/reference/server_config)의 기본값과 같고, 이 실습 VM 을 돌리는 호스트 클러스터(10.244/10.96)와 겹치지 않아 그대로 씁니다.
profile: cis 를 켜면 바뀌는 것은 [Pod Security Standards 문서](https://docs.rke2.io/security/pod_security_standards)에 정리돼 있습니다. 같은 파일이 이렇게 다시 쓰였습니다(실측).
defaults: enforce: "restricted" audit: "restricted" warn: "restricted"exemptions: namespaces: [kube-system, compliance-operator-system, tigera-operator]그 밖에 kubelet 에 protect-kernel-defaults 가 켜져 커널 값이 기대와 다르면 kubelet 이 멈추고(이 판에서는 플래그가 아니라 /var/lib/rancher/rke2/agent/etc/kubelet.conf.d/00-rke2-defaults.conf 의 protectKernelDefaults: true 로 들어갔습니다, 실측), default·kube-public·kube-system 에 NetworkPolicy 가 생깁니다. default ServiceAccount 의 토큰 자동 마운트도 RKE2 가 시스템 네임스페이스에서는 끕니다(이 VM 에서는 default·kube-public·kube-system·kube-node-lease 가 false 였습니다, 실측). 하지만 가이드는 운영자가 나중에 만든 네임스페이스의 NetworkPolicy 와 default ServiceAccount 를 "운영자 개입 필요" 로 남겨 둡니다. 실습에서 만든 네임스페이스 두 개는 NetworkPolicy 도 없고 automount 값도 비어 있었습니다(실측).
현장에서 만나는 모습
기본 프로필로 몇 달 돌던 클러스터에 점검을 앞두고 profile: cis 를 넣고 재시작했다고 해 봅시다. 이 VM 에서 순서대로 일어난 일입니다(실측).
1) level=fatal msg="missing required: user: unknown user etcd ..." ← 시작 거부2) systemctl stop → etcd 사용자·sysctl 준비 → 다시 시작 → etcd 컨테이너: failed to open database .../member/snap/db ... panic3) 데이터 디렉터리는 etcd:etcd 로 바뀌었는데 member/snap/db 만 root:root4) rke2-killall.sh 로 남은 컨테이너까지 내리고 다시 시작 → 9초 만에 readyz ok, db 도 etcd:etcd원인은 systemctl stop 이 rke2 프로세스만 멈추고 정적 파드 컨테이너는 남긴다는 데 있었습니다. stop 뒤에도 root 로 도는 etcd 가 그대로 있었고(실측), 새로 시작한 RKE2 가 보낸 Defragmenting etcd database 를 그 옛 etcd 가 받아 db 파일을 root 소유로 다시 썼습니다. 그다음 etcd 사용자로 뜬 새 etcd 는 그 파일을 열지 못했습니다. 로그 시각과 파일 소유자로 본 인과입니다. 반대로 옛 컨테이너를 정리한 뒤에는 chown 을 하지 않아도 RKE2 가 시작하며 소유권을 맞췄습니다(실측) — 가이드가 말하는 "etcd 데이터 디렉터리를 etcd 소유로 만든다" 가 이것입니다.
API 가 돌아온 뒤에도 끝이 아니었습니다. RKE2 가 NetworkPolicy 를 써 넣은 것은 시작 뒤 62초, 재시작 뒤 처음 만든 네임스페이스에 default ServiceAccount 가 생긴 것은 네임스페이스를 만들고 16초 뒤(다른 VM 에서는 43초)였습니다(실측). 그 사이에 파드를 넣으면 PodSecurity 가 아니라 serviceaccount "default" not found 로 거절되어, 정책 때문에 막힌 것처럼 보입니다.
2번이 특히 헷갈립니다. 설정 파일은 전부 맞게 고쳤고 서비스는 activating 인데 kubectl 은 연결 시간 초과만 냅니다. 원인은 서비스 로그가 아니라 etcd 컨테이너 로그(/var/log/pods/kube-system_etcd-*)에만 있습니다.
파드 쪽에서는, 기본 프로필에서 그대로 떴던 특권 파드와 같은 매니페스트가 새 네임스페이스에서 violates PodSecurity "restricted:latest" 로 거절됩니다. 하지만 이미 떠 있던 특권 파드는 계속 Running 입니다. PSA 는 생성 요청만 검사하기 때문에, 프로필을 켠 날에는 조용하다가 그 파드가 재배포되는 날 갑자기 막힙니다. k3s 에서 옮겨 온 워크로드도 마찬가지로, RKE2 라서가 아니라 cis 프로필이 켜진 RKE2 라서 거절됩니다 — 기본 프로필의 RKE2 는 받아 줍니다.
또 하나, 설치 경로도 환경을 탑니다. 설치 스크립트는 /usr/local 이 읽기 전용이거나 별도 마운트 지점이면 /opt/rke2 에 풉니다([빠른 시작 문서](https://docs.rke2.io/install/quickstart)와 스크립트 주석). 이 실습 VM 은 /usr/local 이 큰 스크래치 디스크이고 /opt 가 있는 루트는 2.4GiB 에 이미 89% 였습니다(실측, 설치물은 128M). 그대로 두면 루트가 거의 차므로 INSTALL_RKE2_TAR_PREFIX 를 명시했습니다.
실무에서 진짜 중요한 것
- cis 프로필은 처음 세울 때 켭니다. 하드닝 가이드도 RKE2 를 설치했지만 아직 돌리지 않은 상태를 전제로 합니다. 호스트 준비(etcd 사용자, sysctl 파일)를 먼저 하고 첫 시작부터 프로필을 줍니다. 나중에 켜야 한다면 서비스만 멈추지 말고
rke2-killall.sh로 정적 파드까지 내린 뒤 시작합니다. - 돌고 있는 노드에서 systemd-sysctl 을 재시작하지 않습니다. 하드닝 가이드가 CNI 가 설정한 커널 값과 충돌하는 부작용을 경고합니다. 파일 하나만
sysctl -p로 적용합니다. - PSA 는 과거를 검사하지 않습니다. 프로필을 켠 뒤에는 기존 워크로드를 서버 dry-run 으로 다시 넣어 보는 목록이 필요합니다.
- 스냅숏은 기본으로 12시간마다 5개를 남깁니다. 프로필 변경 같은 큰 작업 전에는
rke2 etcd-snapshot save로 이름 붙은 스냅숏을 따로 뜨고, [백업과 복구 문서](https://docs.rke2.io/datastore/backup_restore)대로 서버 토큰(/var/lib/rancher/rke2/server/token)을 함께 보관합니다. 문서에 따르면 스냅숏 안의 부트스트랩 데이터(CA 인증서 같은 기밀 데이터)는 그 토큰으로 복호화하므로, 다른 호스트로 복구할 때 토큰이 없으면 쓸 수 없습니다. 위치도 확인이 필요합니다. 문서의 플래그 표는 기본 위치를${data-dir}/db/snapshots로 적지만, 이 VM 에서 파일은/var/lib/rancher/rke2/server/db/snapshots에 있었습니다(실측).
다음 실습에서 할 것
VM 의 RKE2 에서 설치 위치와 기본 구성요소를 기록하고, 기본 프로필에서 특권 파드를 띄운 뒤 profile: cis 로 바꿉니다. 시작 거부 메시지를 받고, 정적 파드까지 정리한 뒤 etcd 사용자·sysctl 을 준비해 다시 올리고 소유권이 어떻게 바뀌었는지 확인한 다음, 같은 매니페스트가 거절되는 것과 기존 파드가 남는 것을 확인하고 스냅숏을 떠서 보고서로 정리합니다.