| 항목 | 내용 |
|---|---|
| 프로젝트 | Kubernetes |
| 관련 릴리스 | v1.24(SELinuxMountReadWriteOncePod alpha) → v1.27(beta) → v1.36 "ハル(Haru)"(2026-04-22, SELinuxMountReadWriteOncePod 및 SELinuxChangePolicy GA) |
| KEP | KEP-1710: Speed up SELinux volume relabeling using mounts (SIG Storage / SIG Node) |
| GA 승격 PR | kubernetes/kubernetes#136912 — Promote SELinuxChangePolicy & SELinuxMountReadWriteOncePod to GA (2026-03-03 머지) |
| 기능 게이트 | SELinuxMountReadWriteOncePod(GA, LockToDefault: true, v1.39에 게이트 자체 제거 예정), SELinuxChangePolicy(GA, 동일) — 더 넓은 범위를 다루는 SELinuxMount는 이번 릴리스에서 여전히 Beta(기본 비활성), GA는 v1.37 예정 |
| 발견 출처 | Kubernetes v1.36 릴리스 블로그, SELinux Volume Label Changes goes GA |
| 검증 근거 | KEP-1710 README 원문, PR #136912 실제 diff(pkg/features/kube_features.go의 LockToDefault 변경), pkg/volume/util/selinux.go 소스(VolumeSupportsSELinuxMount, GetMountSELinuxLabel, AddSELinuxMountOption) |
Kubernetes v1.36에서 ReadWriteOncePod(RWOP) 볼륨에 한해, 컨테이너 런타임이 볼륨의 모든 파일을 순회하며 SELinux 라벨을 다시 씌우는 대신 마운트 시점에 커널이 한 번에 라벨을 적용하는 방식(SELinuxMountReadWriteOncePod)이 GA로 승격됐다. 동시에 이 최적화를 명시적으로 거부할 수 있는 Pod 필드 SELinuxChangePolicy도 함께 GA가 됐다. v1.24에서 alpha로 시작해 8개 릴리스를 거친, 3단계로 나뉜 롤아웃 계획(KEP는 이를 Phase 1~3으로 명시한다)의 첫 두 단계가 이번에 고정(lock)된 것이고, 모든 볼륨 타입으로 범위를 넓히는 마지막 단계(SELinuxMount)는 아직 Beta로 남아 v1.37을 기다리고 있다.
SELinux가 enforcing 모드인 노드에서, 컨테이너 런타임은 각 컨테이너 프로세스에 system_u:system_r:container_t:s0:c309,c383처럼 카테고리(cXXX,cYYY)까지 포함한 고유한 SELinux 라벨을 부여하고, 그 컨테이너가 쓰는 볼륨의 모든 파일에도 대응하는 파일 라벨(container_file_t:s0:c309,c383)을 붙인다. 커널은 프로세스 라벨과 파일 라벨의 카테고리가 일치하는 경우에만 접근을 허용한다 — 그래서 컨테이너 탈출에 성공한 프로세스가 있어도, 그 프로세스의 라벨과 카테고리가 다른 다른 컨테이너의 파일에는 (UID 0으로 실행 중이더라도) 접근할 수 없다.
지금까지 kubelet은 볼륨이 SELinux를 지원하는지 알고 있으면(in-tree 플러그인은 하드코딩된 목록으로, CSI는 마운트 옵션에 seclabel이 있는지로 판단) 컨테이너 런타임에게 CRI의 selinux_relabel(도커 시절 :Z 옵션과 동일한 의미)을 전달했다. 런타임은 이 신호를 받으면 볼륨의 모든 파일을 순회하며 Pod가 요청한 라벨(또는 런타임이 새로 할당한 무작위 라벨)로 다시 라벨링했다. 이 방식은 파일 개수에 비례해 시간이 걸리고(대용량 볼륨·원격 파일시스템에서 특히 느림), 라벨을 저장할 여유 공간이 거의 없는 볼륨에서는 실패할 수 있고, 애초에 쓰기가 불가능한 읽기 전용 볼륨에는 적용할 방법이 없었다.
권한 있는(privileged) 컨테이너는 예외다. 이들은 카테고리가 없는 spc_t("super privileged container") 라벨을 받아 호스트의 어떤 파일에도 접근할 수 있으므로, 런타임은 이들에 대해 재라벨링 자체를 수행하지 않는다.
리눅스 커널은 mount -o context=<라벨> <원본> <대상> 옵션을 지원한다. 이 옵션으로 마운트하면, 실제로 파일마다 라벨을 다시 쓰지 않고도 그 마운트 아래 모든 파일이 마치 지정한 라벨을 가진 것처럼 취급된다. 단, 몇 가지 제약이 있다: 그 볼륨의 첫 마운트여야 하고(두 번째 마운트부터는 실패하거나 무시된다), -o context로 마운트된 파일에는 seclabel 마운트 옵션이 붙지 않으며, 이후 chcon으로 개별 파일 라벨을 바꿀 수도 없다. KEP-1710은 바로 이 커널 기능을 Kubernetes가 마운트 단계에서 활용하도록 만드는 작업이다.
[변경 전] 컨테이너 런타임이 볼륨을 통째로 재라벨링
kubelet CRI (containerd/CRI-O)
│ selinux_relabel=true │
│ (":Z" 힌트) ───────────▶ │
│ │ 볼륨 루트부터 재귀 순회
│ │ ┌──────┐ ┌──────┐ ┌──────┐
│ │ │file 1│ │file 2│ │ ... │ 파일마다 setxattr()
│ │ └──────┘ └──────┘ └──────┘
│ │ (파일 수 O(n)에 비례한 시간)
│ ▼
│ 라벨 적용 완료 후 컨테이너 시작
문제:
- 대용량/원격(NFS 등) 볼륨일수록 컨테이너 시작 지연이 커진다.
- 거의 가득 찬 볼륨은 라벨 xattr을 쓸 공간이 없어 재라벨링이 실패할 수 있다.
- 읽기 전용 볼륨은 애초에 재라벨링할 수 없다.
pkg/volume/util/selinux.go의 GetMountSELinuxLabel()은 Pod의 모든 컨테이너가 이 볼륨에 대해 같은 SELinux 라벨을 쓰는지 확인하고(다르면 MultipleSELinuxLabelsError로 거부), SELinuxChangePolicy: Recursive로 명시적 opt-out하지 않았는지, 그리고 볼륨 플러그인이 -o context 마운트를 지원하는지(SupportsSELinuxContextMount — CSI는 CSIDriver.Spec.SELinuxMount: true로 스스로 선언해야 하고, in-tree는 iSCSI·FibreChannel만 하드코딩으로 지원)를 확인한다. 이 조건을 모두 만족하면 AddSELinuxMountOption()이 마운트 옵션 목록에 context="<라벨>"을 추가하고, 커널이 마운트 시점에 볼륨 전체에 라벨을 적용한다 — 이후 kubelet은 CRI에 selinux_relabel을 전달하지 않으므로 런타임 쪽 재귀 순회 자체가 생략된다.
이번 GA 승격의 핵심은 범위를 ReadWriteOncePod(RWOP) 볼륨으로 한정했다는 점이다. VolumeSupportsSELinuxMount()를 보면, SELinuxMount(전체 볼륨 대상) 게이트가 꺼져 있어도 볼륨의 AccessMode가 ReadWriteOncePod 하나뿐이면 마운트 옵션 방식을 적용한다. RWOP 볼륨은 정의상 동시에 하나의 Pod만 쓸 수 있으므로, "서로 다른 라벨을 가진 두 Pod가 같은 볼륨을 동시에 마운트하려는" 충돌 시나리오 자체가 성립하지 않는다 — 그래서 opt-out 없이도 안전하게 기본값으로 고정(LockToDefault: true)할 수 있었다.
[변경 후] RWOP 볼륨은 마운트 시점에 커널이 라벨을 적용한다
kubelet 커널 (마운트 시스템 콜)
│ GetMountSELinuxLabel() │
│ ├─ 컨테이너들의 라벨이 모두 같은가? │
│ ├─ SELinuxChangePolicy: Recursive? │
│ └─ 볼륨/CSIDriver가 SELinuxMount 지원? │
│ │ 모두 통과 (RWOP는 항상 통과 가능)
│ ▼
│ mount -o context="system_u:...:s0:c10,c0" <src> <dst>
│ ──────────────────────────────────▶ │ 파일 순회 없이 즉시 라벨 적용 (O(1))
│ ▼
│ CRI에 selinux_relabel 전달 안 함
│ (런타임 쪽 재귀 재라벨링 생략)
RWOP이 아닌 볼륨(일반 PVC 등)은 여전히 기존 방식(런타임 재귀 재라벨링)을 그대로 쓴다.
왜 한 번에 모든 볼륨으로 넓히지 않고 RWOP부터 GA로 고정했는가: KEP는 이 작업을 Phase 13으로 나눴는데, Phase 1·2(1.31에서 SELinuxMountReadWriteOncePod)는 RWOP로 범위를 한정해 "여러 Pod가 같은 볼륨을 다른 라벨로 동시에 쓰는" 충돌이 원천적으로 불가능하게 만들었다. 반면 모든 볼륨으로 넓히는 Phase 3(SELinuxMount)은 실제로 privileged Pod와 unprivileged Pod가 같은 볼륨을 병행 사용하는 워크로드를 깨뜨릴 수 있다 — KEP는 이 문제가 "1.30SELinuxMountReadWriteOncePod가 베타로 기본 활성화됐을 때 실제로 발견됐다"고 명시한다. 그래서 위험이 없는 범위(RWOP)부터 먼저 GA로 고정하고, 위험이 있는 범위는 별도 게이트로 분리해 한 릴리스 늦게(v1.37) 승격시키는 점진적 전략을 택했다.
왜 SELinuxChangePolicy라는 새 Pod 필드를 굳이 먼저 GA시켰는가: 이 필드는 아직 GA도 아닌 SELinuxMount(전체 볼륨 확장)를 위한 opt-out 수단인데도 한 릴리스 먼저(v1.36) 고정됐다. KEP는 이를 의도적인 순서로 설명한다 — "클러스터 관리자가 SELinuxMount 게이트가 기본 활성화된 버전으로 업그레이드하기 전에 문제 있는 Pod를 고쳐 opt-out을 걸어둘 수 있어야 한다"는 것이다. 필드를 만들 위치로 FSGroupChangePolicy가 있는 PodSecurityContext를 재사용한 이유도 나와 있다 — PVC는 플랫폼 종속적 필드를 둘 수 없는 리소스이고, PV는 사용자가 직접 편집할 수 없는 리소스라 사용자가 조정할 수 있는 지점이 Pod 스펙뿐이었다.
왜 게이트 자체를 GA와 동시에 잠갔는가(LockToDefault: true): PR #136912의 diff는 두 게이트 모두에 LockToDefault: true를 추가하며 주석으로 "v1.39에 제거 예정"이라고 남겼다. 이는 Kubernetes의 일반적인 GA 관례를 그대로 따른 것 — 기능이 안정화되면 게이트를 꺼서 되돌릴 수 있는 옵션 자체를 없애고, 몇 릴리스 뒤 게이트 코드를 완전히 지운다. RWOP 범위는 충돌 시나리오가 없다고 설계로 보장했기 때문에, 되돌릴 안전판을 남겨둘 이유가 없다고 판단한 것이다.
[변경 후] 롤아웃 단계별 위험 분리
Phase 1/2: SELinuxMountReadWriteOncePod Phase 3: SELinuxMount (v1.37 예정)
범위: RWOP 볼륨만 범위: 모든 볼륨
충돌 가능성: 없음 (동시 사용 불가) 충돌 가능성: 있음 (privileged+unprivileged 등)
v1.36: GA, LockToDefault v1.36: Beta, 기본 비활성
│ │
└── 같은 릴리스에 opt-out 필드도 GA ────────────────┘
(SELinuxChangePolicy: Recursive)
→ v1.37에서 SELinuxMount가 기본 켜지기 전에
관리자가 미리 문제 있는 Pod에 걸어둘 수 있다
- v1.37에서
SELinuxMount가 기본 켜지면, privileged/unprivileged Pod를 혼용하는 기존 클러스터 중 얼마나 많은 비율이ContainerCreating에 멈춰 opt-out을 뒤늦게 추가해야 할까? - KEP가 언급한
SELinuxController(kube-controller-manager에 새로 추가되는, 충돌 가능성이 있는 Pod를 사전에 지표·이벤트로 알려주는 컴포넌트)는 실제로 어느 릴리스에 나올까? - RWOP는 성립하지 않지만 subPath로 같은 볼륨을 나눠 쓰던 기존 패턴(서로 다른 라벨의 Pod가 subPath로 공존)이
SELinuxMountGA 이후 얼마나 발견될까?
- KEP-1710: Speed up SELinux volume relabeling using mounts (kubernetes/enhancements)
- kubernetes/kubernetes#136912 — Promote SELinuxChangePolicy & SELinuxMountReadWriteOncePod to GA
- kubernetes/kubernetes
pkg/volume/util/selinux.go - Kubernetes v1.36: ハル(Haru) 릴리스 블로그
- SELinux Volume Label Changes goes GA (and likely implications in v1.37)
본문 참고 자료로 충분했다.