TT Lab
요금제

한국어

시작하기
블로그

블로그

k3s·RKE2·k0s 보안과 운영: CIS·FIPS 기본값, 업그레이드, 에어갭, 백업

상태: 초안 (2026-10-13), 공식 문서 기준 + 일부 실측. 보안 기본값과 운영 절차는 공식 문서를 2026-10-13 에 열어 확인했습니다. 「실측」은 TT Lab 코스의 실습 VM 에서 겪은 결과이고, 판을 함께 적었습니다. 세 배포판에 같은 점검 도구(kube-bench 등)를 돌려 통과 개수를 비교하는 실험은 하지 않았습니다(미실측). 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 문서에서 찾지 못한 것은 「미확인」으로 표시합니다.

목차

1. 보안 기본값 한눈에

1편에서 본 구조 차이는 보안에서 더 크게 벌어집니다. 먼저 표로 봅니다.

항목 k3s RKE2 k0s
CIS 벤치마크 기본으로 많은 항목 통과, 나머지는 하드닝 가이드대로 직접 설정 profile: cis 로 대부분 자동 적용, 호스트 준비는 운영자 몫 kube-bench 의 k0s-1.0 벤치마크를 몇 가지 예외만 빼고 통과한다고 문서에 적음
FIPS 140 공식 문서에서 찾지 못함(미확인) FIPS 140-2, Go BoringCrypto 빌드 오픈소스 판은 범위 밖, 상업 계약으로만
SELinux k3s-selinux 정책, --selinux 로 켬 rke2-selinux 정책, RPM 설치면 자동 워커의 containerd 에서 켜는 안내 있음
파드 보안(PSA) 기본 따로 설정하지 않으면 쿠버네티스 기본(privileged) 기본 privileged, cis 프로필이면 restricted k0s 자체 기본값은 문서에 없음(미확인)
컨트롤 플레인 실행 사용자 한 프로세스 기본 root, cis 프로필이면 etcd 를 etcd 사용자로 etcd·apiserver·scheduler·konnectivity 를 각각 별도 사용자로
그 밖 감사 로그·비밀 암호화는 직접 켬 빌드 파이프라인에서 Trivy 로 CVE 검사 텔레메트리 기본 켜짐(실측, v1.36.4)

표만 보면 RKE2 가 앞서 보이지만, 세 배포판이 겨냥하는 바가 다를 뿐입니다. 각자가 무엇을 자동으로 해 주고 무엇을 운영자에게 남기는지가 핵심입니다.

2. k3s: 대부분은 되어 있고, 나머지는 가이드대로

k3s 하드닝 가이드는 기본 설정만으로도 CIS 통제 다수를 통과하지만 전부는 아니라는 데서 출발합니다. k3s 는 호스트 OS 를 바꾸지 않기 때문에 호스트 쪽 설정은 운영자가 합니다. 가이드가 요구하는 것은 대략 이렇습니다.

가이드를 다 따라도 사람이 판단해야 하는 항목이 몇 개 남는다고 문서가 따로 표시합니다. FIPS 는 문서에서 찾지 못했습니다. docs.k3s.io/security/fips 는 404 였고 보안 절 목록에도 없었습니다(2026-10-13). FIPS 가 필요하면 k3s 가 아니라 RKE2 를 보라는 뜻으로 읽는 편이 안전합니다.

SELinux 는 지원합니다. 설치 스크립트가 호환되는 시스템에서 k3s-selinux RPM 을 깔고, --selinux 플래그나 설정 파일로 켭니다. 단, SELinux 아래에서는 데이터 디렉터리를 바꾸는 --data-dir 를 지원하지 않습니다.

3. RKE2: 한 줄로 켜지만, 호스트가 준비돼 있어야 켜진다

RKE2 의 보안 기본값은 profile: cis 한 줄에 모여 있습니다. 쿠버네티스 판에 맞는 CIS 벤치마크를 따라가고(가이드 기준 v1.35 이상은 CIS v2.0), 켜면 이런 일이 일어납니다.

운영자 몫으로 남는 것은 두 가지입니다. 설치물에 들어 있는 CIS 용 sysctl 파일을 /etc/sysctl.d/ 에 두는 일, 그리고 etcd 시스템 사용자를 만드는 일입니다. 고쳐 주지 않고 멈추는 쪽을 고른 이유는, 배포판이 호스트 설정을 몰래 바꾸면 같은 호스트의 다른 소프트웨어를 깨뜨릴 수 있기 때문입니다.

실측: 돌던 클러스터에 cis 를 켜면 생기는 일

TT Lab 의 RKE2 실습(v1.36.4+rke2r1, 2026-09-15)에서 기본 프로필로 돌던 서버에 profile: cis 를 넣고 재시작했습니다. 순서대로 이렇게 됐습니다(실측).

  1. missing required: user: unknown user etcd 로 시작을 거부했습니다. 예상대로입니다.
  2. systemctl stop 으로 멈추고 etcd 사용자와 sysctl 을 준비해 다시 시작했더니, 이번에는 etcd 가 데이터베이스 파일을 열지 못해 panic 했습니다.
  3. 원인은 1편에서 본 정적 파드였습니다. systemctl stop 뒤에도 root 로 도는 옛 etcd 컨테이너가 남아 있었고, 새로 시작한 RKE2 가 보낸 조각 모음 요청을 그 옛 etcd 가 받아 db 파일을 root 소유로 다시 썼습니다. 그다음 etcd 사용자로 뜬 새 etcd 는 그 파일을 열 수 없었습니다.
  4. rke2-killall.sh 로 남은 컨테이너까지 내리고 시작하자 9초 만에 API 가 준비됐고, RKE2 가 데이터 디렉터리 소유권을 스스로 맞췄습니다.

PSA 쪽에도 함정이 있었습니다. 기본 프로필에서 PSA 설정 파일은 enforce privileged 였고 특권 파드가 그대로 떴습니다. cis 로 바꾼 뒤 같은 매니페스트는 새 네임스페이스에서 거절됐지만, 이미 떠 있던 특권 파드는 계속 Running 이었습니다(실측). PSA 는 생성 요청만 검사하기 때문입니다. 프로필을 켠 날은 조용하다가 그 파드를 다시 배포하는 날 막힙니다.

교훈은 둘입니다. cis 프로필은 처음 세울 때 켭니다. 나중에 켜야 한다면 서비스만 멈추지 말고 정적 파드까지 내린 뒤 켭니다.

FIPS 와 SELinux

RKE2 의 FIPS 140-2 지원은 Go 컴파일러의 BoringCrypto 모듈(NIST 인증 번호 4407)로 빌드한 바이너리에서 나옵니다. 문서에는 140-2 만 있고 140-3 언급은 없었습니다(미확인). CNI 가운데 FIPS 용으로 다시 빌드되는 것은 canal 이라고 적혀 있는데, 같은 페이지의 구성요소 목록에는 Calico 도 있어 문서 안에서 맞지 않는 부분이 있습니다. FIPS 감사를 앞두고 있다면 고를 CNI 를 SUSE 에 직접 확인하는 편이 좋습니다.

SELinux 는 rke2-selinux 정책과 selinux: true 가 필요하고, RPM 으로 설치하면 둘 다 자동으로 처리됩니다. tarball 설치라면 직접 합니다.

4. k0s: 사용자를 나누고, 나머지는 고르게 한다

k0s 는 다른 방향에서 보안에 접근합니다. 1편에서 본 것처럼 컨트롤 플레인 구성요소를 프로세스마다 다른 시스템 사용자로 돌리고, 컨트롤러 노드에는 kubelet 과 컨테이너 런타임이 없어 워크로드가 컨트롤 플레인 옆에 앉을 수 없습니다.

CIS 문서는 kube-bench 의 k0s-1.0 벤치마크로 점검하면 기본 상태에서 몇 가지 예외만 빼고 통과한다고 적습니다. 예외로 든 것은 EventRateLimit, AlwaysPullImages(에어갭 환경과 충돌), 감사 로그와 감사 정책, 암호화 프로바이더, protect-kernel-defaults, kubelet 서비스 파일 항목 등입니다. 통과·실패 개수는 문서에 없습니다.

FIPS 는 오픈소스 프로젝트의 범위 밖입니다. 2025-12 에 병합된 변경(PR #6777)이 README 에서 FIPS 언급을 지우며 그렇게 이유를 댔고, 홈페이지는 「요청과 상업 계약」이 있을 때 제공한다고 적습니다.

PSA 는 spec.api.extraArgs 로 AdmissionConfiguration 을 넘기는 방법을 안내합니다. 예시의 기본값은 privileged 이지만, k0s 자체의 기본 수준을 직접 적은 문장은 찾지 못했습니다(미확인).

하나 더, 텔레메트리가 기본으로 켜져 있습니다. TT Lab 실습 VM(v1.36.4+k0s.0)에서 k0s config create 의 기본 출력에 telemetry 가 켜짐으로 나왔습니다(실측). 외부로 지표를 보내면 안 되는 환경이라면 설정에서 끄고 시작합니다.

5. 설치

k3s RKE2 k0s
기본 설치 get.k3s.io 스크립트 get.rke2.io 스크립트(tarball 또는 RPM) get.k0s.sh 로 받고 k0s install controller
여러 노드를 한 번에 노드마다 스크립트 + 토큰 노드마다 스크립트 + 토큰 k0sctl: 설정 파일 하나로 SSH 를 건너 설치·가입
kubeconfig /etc/rancher/k3s/k3s.yaml /etc/rancher/rke2/rke2.yaml k0s kubeconfig admin 으로 꺼냄
kubectl 함께 깔림 /var/lib/rancher/rke2/bin(기본 PATH 밖) k0s kubectl(따로 깔지 않음)

k3s 와 RKE2 는 「스크립트를 노드마다 돌린다」는 같은 모델입니다. 실습에서 k3s 는 설치 8초, 노드 Ready 11초였습니다(실측, v1.35.8+k3s1, 이미 VM 이 뜬 뒤부터 잼).

k0s 는 k0sctl 이 차별점입니다. 호스트 목록과 역할을 적은 k0sctl.yaml 하나로 k0sctl apply 를 치면 SSH 로 각 호스트에 들어가 설치와 가입을 합니다. 노드를 더하는 것은 되지만, 빼는 것은 k0sctl 이 해 주지 않는다고 문서가 적습니다.

RKE2 설치 스크립트에는 환경을 타는 부분이 하나 있습니다. /usr/local 이 읽기 전용이거나 별도 마운트 지점이면 /opt/rke2 에 풉니다. 실습 VM 은 루트 디스크가 작아 설치물(128M, 실측)이 루트를 거의 채울 뻔했고, INSTALL_RKE2_TAR_PREFIX 로 위치를 정해 줘야 했습니다.

6. 업그레이드

① k3s · RKE2 — system-upgrade-controller 의 Plan 둘 Plan: server channel 또는 version Plan: agent prepare 로 server 를 기다림 서버를 하나씩(concurrency 1) (cordon) → 바이너리 교체 → 재시작 서버가 다 끝난 뒤 시작 동시에 몇 대씩 할지 정한다 수동이면 설치 스크립트를 판을 지정해 다시 돌린다. 실측(k3s 1.34→1.35): 교체 11초, 노드 Ready 12초, 파드 그대로 ② k0s — autopilot 의 Plan 하나 Plan: autopilot (이름이 반드시 autopilot) k0supdate: 바이너리 교체 · airgapupdate: 이미지 번들 교체 컨트롤러를 하나씩 매번 전부 /ready 인지 확인 그다음 워커 기본 동시성 1 k0sctl 로 SSH 를 건너 바깥에서 올리는 길도 있다. 공통: 컨트롤 플레인 먼저. k3s·RKE2 문서는 마이너 판 건너뛰기를 금한다.
그림: k3s·RKE2 의 system-upgrade-controller 와 k0s autopilot 의 업그레이드 순서

k3s 와 RKE2 는 같은 도구를 씁니다. 수동이면 설치 스크립트를 판(INSTALL_K3S_VERSION 등)이나 채널을 지정해 다시 돌립니다. 이때 처음 설치할 때 준 인자를 다시 주지 않으면 사라지므로, 설정은 인자보다 설정 파일에 두는 편이 안전합니다. 자동이면 system-upgrade-controller 에 Plan 을 둘 둡니다. 서버용 Plan 이 서버를 하나씩 올리고, 에이전트용 Plan 은 prepare 단계에서 서버 Plan 이 끝나기를 기다렸다가 시작합니다. Plan 에 channel 을 주면 새 판을 따라가고 version 을 주면 그 판에 고정됩니다. 두 문서 모두 마이너 판을 건너뛰지 말라고 적습니다.

실습에서 k3s v1.34.11 을 v1.35.8 로 스크립트로 올렸을 때 11초, 노드 Ready 까지 12초였고, 돌던 파드는 UID·컨테이너 ID·재시작 횟수까지 그대로였습니다(실측). 그런데 같은 실습에서 놓치기 쉬운 점도 확인했습니다. stable 채널은 그날(2026-09-15) 이미 v1.36 을 가리켜, 1.34 에서 채널을 따라가면 마이너 두 칸을 한 번에 건너뛰게 됩니다. 설치 스크립트도 Plan CRD 도 그것을 막지 않았습니다(실측). 자동 업그레이드에는 채널보다 판 고정을 권합니다.

k0s 는 바이너리를 바꾸고 재시작하는 것이 기본이고, 자동화는 autopilot 이 맡습니다. 이름이 반드시 autopilot 인 Plan 하나에 무엇을 바꿀지(k0supdate 로 바이너리, airgapupdate 로 에어갭 이미지 번들)를 적습니다. autopilot 은 컨트롤러를 한 대씩 올리면서 매번 모든 컨트롤러가 준비됐는지 확인하고, 그다음 워커를 기본 한 대씩 올립니다. 바이너리의 sha256 검증도 걸 수 있습니다. 바깥에서 SSH 로 올리고 싶으면 k0sctl 을 씁니다.

7. 에어갭 설치

인터넷이 없는 곳에 세우는 방법은 셋 다 비슷합니다. 바이너리와 이미지 묶음을 미리 옮기고, 정해진 폴더에 두거나 사설 레지스트리를 씁니다.

이미지를 두는 곳 그 밖의 방법
k3s /var/lib/rancher/k3s/agent/images/ 사설 레지스트리 + registries.yaml, 내장 분산 미러(Spegel)
RKE2 /var/lib/rancher/rke2/agent/images/ 사설 레지스트리 + registries.yaml, Hauler, 내장 미러
k0s 워커의 /var/lib/k0s/images/ k0s airgap list-images 로 목록을 뽑아 직접 묶기

옮겨야 할 양은 3편의 자원 표에서 GitHub 릴리스 파일 크기로 비교합니다. 미리 말하면 RKE2 가 가장 무겁습니다. 바이너리 말고도 kubelet·containerd 를 담은 런타임 이미지와 CNI 이미지를 함께 옮겨야 하기 때문입니다.

8. 백업과 복원

k3s RKE2 k0s
기본 저장소의 백업 SQLite: server/db/ 폴더 복사 etcd 스냅숏 자동 k0s backup(etcd 또는 kine/SQLite)
자동 스냅숏 etcd 일 때 12시간마다, 5개 보관 12시간마다, 서버마다 5개 보관 문서에서 찾지 못함(미확인)
S3 로 보내기 --etcd-s3* --etcd-s3* - 로 표준 출력에 흘려 직접
복원 --cluster-reset --cluster-reset-restore-path 같은 방식 k0s restore <파일>
함께 보관할 것 서버 토큰 서버 토큰 묶음에 PKI 포함

k3s 와 RKE2 는 토큰을 반드시 함께 보관해야 합니다. 스냅숏 안의 부트스트랩 데이터(CA 같은 기밀)를 토큰으로 암호화해 두기 때문에, 다른 호스트로 복원할 때 토큰이 없으면 스냅숏을 쓸 수 없습니다.

k0s 의 백업 묶음에는 PKI, etcd 스냅숏(또는 kine/SQLite 스냅숏), k0s.yaml, 매니페스트, 이미지, 헬름 설정이 들어갑니다. 빠지는 것은 PV 데이터와, kine 뒤에 외부 MySQL·PostgreSQL 을 둔 경우의 데이터입니다. 그 DB 는 DB 쪽에서 따로 백업합니다.

실측: 복원 명령이 성공했는데 아무것도 안 돌아왔다

TT Lab 의 k0s 실습에서 가장 위험했던 장면입니다. k0s stop 뒤 바로 k0s restore <파일> 을 치고 다시 시작하면, 명령은 오류도 경고도 없이 끝나고 클러스터도 올라오는데 백업 뒤에 만든 ConfigMap 이 그대로 남아 있었습니다(실측). etcd 는 데이터 디렉터리가 이미 있으면 그것을 쓰고, 풀어 둔 스냅숏을 무시하기 때문입니다. 순서는 k0s stop → 기존 etcd 데이터 비우기 → k0s restore → k0s start 여야 했습니다. 복원했다고 믿게 되는 것이 가장 위험합니다. 백업은 복원을 시험해 본 뒤에야 백업입니다.

k3s 의 복원은 결과가 더 분명했습니다. --cluster-reset 으로 복원하자 6초 만에 리셋, 시작 뒤 11초 만에 API 가 돌아왔고, 스냅숏 이후의 변경은 사라졌으며 이전 DB 는 etcd-old-<시각> 폴더에 남았습니다(실측, v1.35.8+k3s1).

티티 한마디. 보안 스위치를 켜는 건 한 줄이지만, 그 한 줄이 설 땅(호스트 준비)은 사람이 만들어야 해요. 그리고 백업은 꼭 한 번 복원해 보세요. 「성공」이라고 찍혔는데 아무것도 안 돌아온 적이 있거든요.

참고

모든 문서는 2026-10-13 에 열람했습니다.

로그인하면 좋아요를 누를 수 있습니다

댓글

아직 댓글이 없습니다.

로그인하면 댓글을 쓸 수 있습니다