블로그

어떤 고객이 실제로 자체 VM이 필요할까? 계층형 Docker 격리 계획

모든 고객에게 VM을 쓰는 것은 과합니다. 각 테넌트에게 필요한 격리 수준을 결정하고 그 결정을 자동화하는 방법을 소개합니다.

요약

에이전시는 고객이 자신의 데이터가 다른 테넌트와 얼마나 격리되어 있는지 물을 때 종종 당황합니다. Docker의 네임스페이스와 cgroup은 실제 격리를 제공하지만 하드웨어 경계와는 다릅니다. 모든 고객을 VM에서 실행하거나, 더 나쁘게는 모든 고객을 동일하게 취급하는 대신, 소규모 격리 계층 세트를 구축하고 데이터 민감도, 신뢰도, 규정 준수에 따라 각 고객을 하나의 계층에 매칭하세요. 잠긴 컨테이너(비루트, 기능 제거, seccomp, 읽기 전용 루트)는 대부분의 사이트를 처리합니다. 규제 대상 또는 적대적 워크로드는 VM 또는 VM 내 컨테이너 하이브리드를 사용합니다. 이 게시물은 반복 가능한 결정 흐름, 비교 표, 그리고 추가 격리가 과할 때에 대한 솔직한 시각을 제공합니다.

영업 통화에서 새 고객이 "저희는 의료 업계인데, 저희 데이터가 다른 고객과 격리되어 있다는 것을 보여주세요"라고 말할 때, 다른 어떤 이야기를 하고 싶은 상황에 처한 적이 있나요?

이것이 에이전시의 문제입니다. 완벽한 단일 배포가 아니라, 예산과 위험 프로필, 규정 요구 사항이 다른 수십 명의 고객에게 동일한 안정적인 배포를 반복해야 합니다. 솔직한 버전은 이렇습니다. Docker 격리는 실제이며 구체적입니다. 네임스페이스는 각 컨테이너에 프로세스, 네트워킹, 파일시스템에 대한 자체 보기를 제공하고, cgroup은 CPU, 메모리, 디스크 I/O를 제한하여 테넌트가 서로를 굶기지 못하게 합니다. 그러나 이것이 제공하지 않는 것은 컨테이너와 호스트 커널 사이의 하드웨어 벽입니다. 공격자가 컨테이너를 탈출하면 그들은 당신이 가진 유일한 커널 안에 있게 됩니다. 이 기사의 나머지 부분은 그 불편한 사실을 반복 가능한 결정으로 전환합니다: 각 고객을 데이터 민감도와 신뢰도로 분류하고, 기본 강화 프로필을 적용하며, 침해 비용이 VM 비용보다 높을 때만 VM을 사용합니다.

잠깐, 컨테이너는 이미 격리되어 있지 않나요?

Docker는 Linux 네임스페이스와 cgroup 위에서 실행되며, 이러한 개념은 실제로 중요한 역할을 합니다. 네임스페이스는 프로세스 ID, 네트워크 스택, 마운트 지점, 사용자를 분리하여 한 컨테이너의 프로세스가 다른 컨테이너의 프로세스 테이블을 볼 수 없게 합니다. cgroup은 제한을 설정합니다: 컨테이너에 0.5 CPU, 512MB 메모리, 고정된 블록 I/O 가중치를 주면 정확히 그 만큼만 사용합니다. 한 테넌트의 폭주 루프는 이웃을 다운시키는 대신 제한됩니다. 제한을 구성하지 않았다면 cgroup의 가장 기본적인 용도를 건너뛴 것입니다.

컨테이너 A의 간단한 PHP 앱을 생각해 보세요. 자체 파일시스템, 자체 네트워크 인터페이스, 자체 PID 1을 갖습니다. 컨테이너 B도 동일하지만 다른 보기를 갖습니다. 그것이 네임스페이스입니다. 이제 메모리 제한을 설정하지 않고 방치하면 컨테이너 A가 호스트의 RAM을 가득 채워 컨테이너 B를 극도로 느리게 만들 수 있습니다. cgroup이 이를 방지하기 위해 존재합니다. 그러나 두 컨테이너는 네임스페이스로 서로 격리될 수 있어도 호스트 커널을 공유하며, 이것이 모든 컨테이너 탈출 이야기의 핵심입니다. 커널에 도달하는 익스플로잇은 잠재적으로 해당 호스트의 모든 테넌트에 도달할 수 있습니다.

"Docker는 격리되어 있다"는 반만 맞는 문장입니다. 정확한 표현은 "Docker는 네임스페이스와 cgroup으로 격리하며, 커널 취약점이 폭발 반경이다"입니다. 테넌트가 신뢰할 수 없는 코드를 실행하도록 허용하기 전에 잠시 그 생각을 곱씹어 보세요. 답은 "컨테이너를 절대 사용하지 말자"가 아닙니다. 그것은 쉬운 공황입니다. 답은 계층 시스템입니다.

그렇다면 왜 일부 고객은 네임스페이스 이상이 필요할까요?

솔직한 답은 격리는 스위치가 아니라 스펙트럼이라는 것입니다. 한쪽 끝에는 모든 사람이 사실상 하나의 앱 안에 있는 완전히 공유된 컨테이너가 있고, 다른 쪽 끝에는 자체 커널을 가진 테넌트별 별도 VM이 있습니다. 대부분의 에이전시 작업은 불편한 중간에 있으며, 중간은 "Docker면 충분하다"와 "모두에게 VM을 실행하라" 사이의 이분법적 선택이 아닙니다.

고객을 오른쪽으로 밀어내는 것은 규모가 아닙니다. 네 가지 질문입니다:

  • 규제 대상 데이터를 저장합니까? 건강 기록, 결제 카드 정보, 규제 기관이 민감하다고 부르는 모든 것.
  • 테넌트에서 발생한 침해가 다른 테넌트로 현실적으로 전파될 수 있습니까? 임의 코드를 실행할 수 있다면 그렇습니다.
  • 코드와 이를 배포하는 사람들을 신뢰합니까? 가장 저렴한 프리랜서를 고용한 고객은 당신이 아는 개발 팀을 가진 고객과 같은 신뢰 수준이 아닙니다.
  • 계약서에 "전용", "격리", 또는 "사설"이라고 명시되어 있습니까? 그렇다면 이미 계층을 약속한 것입니다. 이제 올바른 계층을 선택하는 일만 남았습니다.

아직 그 질문에 답할 수 없다면 고객을 기본 계층에 배치하고 가정을 기록하세요. 그것은 보안 감사가 아니라 모든 온보딩에서 반복하는 현실 점검입니다.

매번 보안 감사를 실행하지 않고 고객별로 어떻게 결정할까요?

작은 표를 만들고 그것을 고수하세요. 40개 셀로 된 매트릭스는 필요 없습니다. 네 개의 계층이 에이전시가 만나는 거의 모든 고객을 충당합니다.

고객 위치실제로 구분하는 요소사용 시점
계층 1: 공유 앱/컨테이너애플리케이션 로직만내부 유틸리티, 저위험 데이터, 모든 사람이 명시적으로 하나의 로그인 시스템에 있는 프로젝트
계층 2: 동일 호스트, 별도 컨테이너네임스페이스 및 cgroup대부분의 마케팅 사이트, 문의 양식, 민감한 데이터 없음
계층 3: 잠긴 컨테이너계층 2 + 비루트, 기능 제거, seccomp, 읽기 전용 루트, 네트워크 분리전자상거래, 개인식별정보(PII), 완전히 신뢰하지 않는 맞춤 코드
계층 4: 테넌트별 VM하이퍼바이저 및 별도 커널의료, 금융, 규정 준수 서류, 신뢰할 수 없는 코드, 소음 유발 이웃

실제로는 이렇게 진행됩니다. 문의 양식과 인스타그램 링크가 있는 제과점 고객은 계층 2로 갑니다: 공유 호스트의 컨테이너 하나, 기본 Docker 네트워킹, 리소스 제한, 끝. 고객 이름, 주소, 결제 리디렉션을 저장하는 온라인 스토어는 계층 3으로 갑니다: 동일한 공유 호스트지만 컨테이너는 비루트 사용자로 실행되고, 추가 커널 기능이 없으며, seccomp 프로필을 사용하고, 포트 443만 노출합니다. 보호된 건강 정보를 저장하는 의료 접수 포털은 계층 4로 갑니다: 테넌트별 VM, 침해 비용이 "우리가 정리하면 돼"가 아니라 "고객에게 진지하게 받아들였다는 것을 보여줄 수 없음"이기 때문입니다.

핵심은 모든 고객마다 아키텍처를 다시 생각하는 것이 아니라 이미 합의한 표에서 행을 선택한다는 것입니다. 그래야 5인 에이전시가 수백 개의 사이트를 운영하면서 수백 가지의 보안 집착을 하지 않아도 됩니다. 또한 다음 고객이 어떤 팀원이 전화를 받았는지에 따라 다른 답을 얻지 않게 합니다. 이러한 선택 뒤에 있는 더 깊은 아키텍처 논쟁은 다중 테넌트 격리 수준 설계 가이드에서 더 자세히 다룹니다.

잠긴 컨테이너는 실제로 어떤 모습일까요?

"잠겼다"는 말 대신 구체적으로 알아봅시다. 일반적인 WordPress 또는 PHP 고객에게 계층 3이 의미하는 바는 다음과 같습니다.

첫째, 사용자를 변경하세요. 대부분의 공식 이미지는 여전히 기본적으로 root로 실행됩니다. Dockerfile에서 비루트 사용자를 만들고 해당 사용자로 앱을 실행하세요. 이것만으로도 컨테이너 침해가 호스트 침해로 이어지는 가장 흔한 경로를 제거합니다. 둘째, 필요 없는 기능을 제거하세요. --cap-drop ALL로 실행하고 앱이 80 포트에서 수신할 수 있도록 보통 NET_BIND_SERVICE 하나만 추가하세요. 이것만으로도 대부분이 예상하는 것보다 큰 변화입니다. 셋째, --read-only로 루트 파일시스템을 읽기 전용으로 만들고 쓰기 가능한 디렉토리(업로드, 데이터베이스 데이터 디렉토리)를 볼륨 또는 tmpfs로 마운트하세요. 넷째, seccomp 프로필을 적용하고 호스트가 지원한다면 AppArmor 또는 SELinux를 적용하세요. 마지막으로 컨테이너를 전용 Docker 네트워크에 배치하고 실제로 접근 가능해야 하는 포트만 노출하세요.

WordPress 예시를 살펴보겠습니다. 기본 이미지는 아마 root로 실행되므로 useradd 단계와 USER 지시문을 추가합니다. 메모리 제한과 CPU 제한으로 컨테이너를 실행하여 플러그인 트래픽 폭주가 이웃에게 피해를 주지 않게 합니다. /var/www/html/wp-content/uploads를 쓰기 가능한 볼륨으로 마운트합니다. --read-only를 설정합니다. --privileged 플래그가 전혀 없는 네트워크에 연결합니다. 결과는 이전에는 "WordPress 사이트"였지만 이제는 "대부분의 가상 사설 서버보다 더 잠긴 WordPress 사이트"가 된 컨테이너입니다.

이 모든 것을 수동으로 처리하는 것이 불안정하게 느껴진다면 더 쉬운 중간 경로가 있습니다: 사용자 네임스페이스 격리와 보안 컨테이너 런타임을 사용하는 Docker의 향상된 컨테이너 격리(Enhanced Container Isolation)입니다. 그것은 합법적인 지름길이지만 비루트 또는 기능 제거를 건너뛸 수 있는 면죄부는 아닙니다. 테넌트는 여전히 합리적인 이미지가 필요합니다. 차이점은 하룻밤 사이에 seccomp 전문가가 되지 않고도 커널 노출 공격 표면이 줄어든다는 것입니다. 단일 테넌트에 대한 정확한 절차를 원한다면 단계별 격리 강화 가이드가 이 섹션을 복사-붙여넣기 명령으로 바꿔줍니다.

언제 계층을 더 추가하지 않고 VM을 제공해야 할까요?

반대 의견을 말하자면, 더 많은 격리가 자동으로 더 나은 것은 아닙니다. VM은 하드웨어 수준 격리, 별도 커널, 게스트 커널이 무너질 때 훨씬 더 작은 공격 표면을 제공합니다. 이는 "우리는 격리되길 원합니다"라고 말하는 의료 및 금융 고객이 기대하는 바로 그것입니다. 그러나 모든 VM은 패치, 백업, 컴퓨팅 비용을 추가하고, 팔릿을 업데이트로 유지하는 작업을 배가시킵니다. 한 고객이 Docker가 무섭다고 말했다고 해서 모든 고객을 VM으로 옮긴다면, 실제 비용으로 안전 연극을 산 것입니다.

VM은 테넌트당 위험이 테넌트당 VM의 운영 비용보다 높을 때 올바른 답입니다. 즉, 규제 대상 데이터, 문서화된 규정 준수 요구 사항, 신뢰할 수 없는 타사 코드, 또는 소음 유발 이웃을 제거해야 하는 고객을 의미합니다. 또한 고객의 계약서에 문자 그대로 전용 환경이 명시된 경우에도 올바른 답입니다. "전용"에 서명할 때 그들이 상상하는 것은 "컨테이너"가 아니기 때문입니다.

그러나 VM이 느슨한 컨테이너를 용서하지는 않습니다. 흔한 함정은 고객을 VM에 넣고 "VM이 보호한다"는 이유로 강화를 건너뛰는 것입니다. VM은 테넌트로부터 호스트를 보호하지, 테넌트를 자체의 나쁜 이미지로부터 보호하지 않습니다. 여전히 그 VM 내부에서 비루트, 기능 제거, seccomp를 원합니다. 하이브리드 접근 방식(VM 내부의 컨테이너)은 종종 최적의 지점입니다: VM은 규정 준수 대화를 위한 경계를 제공하고, 컨테이너는 이미 알고 있는 배포 워크플로우를 제공합니다. 이 논쟁에 대한 더 긴 버전은 모든 테넌트가 자체 VM을 가져야 할까?에 있으며, 짧은 답은 VM은 두려움이 아니라 계약을 위한 것이라는 점입니다.

모든 고객에게 이 방식을 반복 가능하게 하려면 어떻게 해야 할까요?

계층 시스템을 기억이 아닌 템플릿으로 만들면 반복 가능해집니다. 계층별로 Compose 파일 디렉토리를 유지하세요: tier2-baseline, tier3-locked, tier4-vm-hybrid. 새 고객이 나타나면 템플릿을 복사하고 환경 변수를 변경하면, 새 인프라를 한 줄도 작성하기 전에 이미 격리 형태를 알 수 있습니다.

그런 다음 결정을 기록하세요. 400페이지 분량의 보안 보고서가 아니라 고객의 저장소에 짧은 단락으로: 어떤 데이터를 저장하는지, 어떤 계층에 있는지, 이유, 그리고 어떤 상황에서 계층을 올릴 수 있는지. 그 단락은 백 개의 방화벽 규칙보다 가치가 있습니다. 다음 감사자나 걱정하는 다음 고객에게 보여줄 수 있는 것이기 때문입니다. 또한 원래 영업 통화가 흐려진 후에 왜 제과점이 계층 2를 받고 전자상거래 매장이 계층 3을 받았는지 기억해야 하는 수고를 덜어줍니다.

지루한 검사를 자동화하세요. CI가 모든 고객 이미지를 스캔하고, root로 실행되거나 모든 기능을 가지고 있거나 계층이 허용하는 것 외의 포트를 게시하려 하면 빌드를 실패하게 하세요. 그 어떤 것도 특별하지 않습니다. 단지 선의의 개발자가 템플릿을 실수로 망가뜨리지 않도록 하는 것입니다. 어차피 주변 호스팅 워크플로우를 구축하고 있다면, 프로덕션 준비 Docker 호스팅 전략이 컨테이너가 정의된 후의 부분을 다룹니다.

이 모든 것은 화려하지 않습니다. 어떤 블로그 게시물도 "테넌트 격리"를 그린필드 아키텍처 다이어그램처럼 흥미진진하게 만들 수 없습니다. 그러나 이것은 "우리는 얼마나 격리되어 있나요?"라는 질문에 손가락을 꼬며 "완전히 격리되어 있습니다"라고 대답하는 에이전시와, 계층, 구성, 이유를 보여줄 수 있는 에이전시의 차이입니다. 컨테이너는 마법의 벽이 아닙니다. VM은 마법의 총알이 아닙니다. 계층 시스템은 기록하고 재사용하는 결정일 뿐입니다. 에이전시에게 반복 가능성이 전부입니다.

Sources (5)