상태: 초안 (2026-10-13), 공식 문서 기준 + 일부 실측. 보안 기본값과 운영 절차는 공식 문서를 2026-10-13 에 열어 확인했습니다. 「실측」은 TT Lab 코스의 실습 VM 에서 겪은 결과이고, 판을 함께 적었습니다. 세 배포판에 같은 점검 도구(kube-bench 등)를 돌려 통과 개수를 비교하는 실험은 하지 않았습니다(미실측). 작성 규칙: 실측 데이터가 기본이고, 못 잰 것은 「미실측」, 문서에서 찾지 못한 것은 「미확인」으로 표시합니다.
목차
- 1. 보안 기본값 한눈에
- 2. k3s: 대부분은 되어 있고, 나머지는 가이드대로
- 3. RKE2: 한 줄로 켜지만, 호스트가 준비돼 있어야 켜진다
- 4. k0s: 사용자를 나누고, 나머지는 고르게 한다
- 5. 설치
- 6. 업그레이드
- 7. 에어갭 설치
- 8. 백업과 복원
- 참고
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 를 바꾸지 않기 때문에 호스트 쪽 설정은 운영자가 합니다. 가이드가 요구하는 것은 대략 이렇습니다.
protect-kernel-defaults: true를 켜고, kubelet 이 기대하는 커널 값(vm.panic_on_oom=0,vm.overcommit_memory=1,kernel.panic=10,kernel.panic_on_oops=1)을 sysctl 파일로 둡니다.- PSA 설정 파일을 만들어 API 서버의
admission-control-config-file로 넘기고, 기본을 restricted 로 둡니다. kube-system 은 예외로 둡니다. - 감사 로그, 비밀 암호화(
secrets-encryption: true), 네임스페이스마다 NetworkPolicy 를 켭니다.
가이드를 다 따라도 사람이 판단해야 하는 항목이 몇 개 남는다고 문서가 따로 표시합니다. 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), 켜면 이런 일이 일어납니다.
- 시작할 때 호스트 요건을 검사하고, 안 되어 있으면 멈춥니다.
- etcd 를
etcd사용자로 돌리고, 파일 권한을 좁힙니다. - 기본 네임스페이스들에 NetworkPolicy 를 써 넣습니다.
- PSA 를 restricted 로 바꿉니다(kube-system 등 예외 셋).
- kubelet 의
protect-kernel-defaults를 강제합니다.
운영자 몫으로 남는 것은 두 가지입니다. 설치물에 들어 있는 CIS 용 sysctl 파일을 /etc/sysctl.d/ 에 두는 일, 그리고 etcd 시스템 사용자를 만드는 일입니다. 고쳐 주지 않고 멈추는 쪽을 고른 이유는, 배포판이 호스트 설정을 몰래 바꾸면 같은 호스트의 다른 소프트웨어를 깨뜨릴 수 있기 때문입니다.
실측: 돌던 클러스터에 cis 를 켜면 생기는 일
TT Lab 의 RKE2 실습(v1.36.4+rke2r1, 2026-09-15)에서 기본 프로필로 돌던 서버에 profile: cis 를 넣고 재시작했습니다. 순서대로 이렇게 됐습니다(실측).
missing required: user: unknown user etcd로 시작을 거부했습니다. 예상대로입니다.systemctl stop으로 멈추고 etcd 사용자와 sysctl 을 준비해 다시 시작했더니, 이번에는 etcd 가 데이터베이스 파일을 열지 못해 panic 했습니다.- 원인은 1편에서 본 정적 파드였습니다.
systemctl stop뒤에도 root 로 도는 옛 etcd 컨테이너가 남아 있었고, 새로 시작한 RKE2 가 보낸 조각 모음 요청을 그 옛 etcd 가 받아 db 파일을 root 소유로 다시 썼습니다. 그다음etcd사용자로 뜬 새 etcd 는 그 파일을 열 수 없었습니다. 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 는 같은 도구를 씁니다. 수동이면 설치 스크립트를 판(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 에 열람했습니다.
- k3s: 하드닝 가이드 https://docs.k3s.io/security/hardening-guide, 고급 설정(SELinux) https://docs.k3s.io/advanced, 빠른 시작 https://docs.k3s.io/quick-start, 수동 업그레이드 https://docs.k3s.io/upgrades/manual, 자동 업그레이드 https://docs.k3s.io/upgrades/automated, 에어갭 https://docs.k3s.io/installation/airgap, 백업·복원 https://docs.k3s.io/datastore/backup-restore, etcd-snapshot https://docs.k3s.io/cli/etcd-snapshot
- RKE2: 하드닝 가이드 https://docs.rke2.io/security/hardening_guide, FIPS https://docs.rke2.io/security/fips_support, SELinux https://docs.rke2.io/security/selinux, Pod Security Standards https://docs.rke2.io/security/pod_security_standards, 설치 방법 https://docs.rke2.io/install/methods, 업그레이드 https://docs.rke2.io/upgrades/manual·https://docs.rke2.io/upgrades/automated, 에어갭 https://docs.rke2.io/install/airgap, 백업·복원 https://docs.rke2.io/datastore/backup_restore
- k0s: CIS https://docs.k0sproject.io/stable/cis_benchmark/, 파드 보안 https://docs.k0sproject.io/stable/podsecurity/, SELinux https://docs.k0sproject.io/stable/selinux/, 설치 https://docs.k0sproject.io/stable/install/, k0sctl https://docs.k0sproject.io/stable/k0sctl-install/, 업그레이드 https://docs.k0sproject.io/stable/upgrade/, autopilot https://docs.k0sproject.io/stable/autopilot/, 에어갭 https://docs.k0sproject.io/stable/airgap-install/, 백업 https://docs.k0sproject.io/stable/backup/, FIPS 언급 삭제 PR https://github.com/k0sproject/k0s/pull/6777
- 쿠버네티스 PSA 기본 수준: https://kubernetes.io/docs/tasks/configure-pod-container/enforce-standards-admission-controller/
- 실측: TT Lab 「쿠버네티스 배포판 — 직접 세운다」 코스의 실습 VM 기록(RKE2 CIS 프로필·k3s 업그레이드·k3s 저장소·k0s 백업 복원·k0s 구성, 2026-08~09)