컨테이너 격리와 네트워크

inuit·2025년 4월 9일

All about 쿠버네티스

목록 보기
5/21
post-thumbnail

최근 업데이트일 2024-11-04

참고자료: Kubernetes 네트워크 이해하기 (2) : 서비스 개념과 동작 원리

쿠버네티스 네트워크를 다루기에 앞서, 컨테이너의 격리 원리와 컨테이너간의 네트워크에 대해서 알아보자.

컨테이너 격리

컨테이너는 격리된 환경에서 실행되는 "프로세스"이고, ‘격리’를 구현하는 원리는 chroot, Linux namespaces, cgroups이다.

chroot

입력된 경로가 루트 디렉토리로 격리된 프로세스를 실행하기 위한 명령어

  • 어떤 디렉토리를 "루트(/)처럼 보이게" 만들어서, 그 안에서 프로그램을 실행한다.
  • 루트 디렉토리 격리를 통해, 프로세스는 자기 루트가 그 디렉토리인 줄 알고 동작한다.
  • 장점
    • 시스템의 /etc/passwd, /var/log, /home 같은 중요 디렉토리에 접근 불가하게 하여 보안성이 증가한다.
    • 실제 시스템에 영향 없이 격리된 테스트 환경을 만들 수 있다.
  • 하지만, 보안 측면에서 완전한 격리는 아니다. root 권한이 있으면 chroot 감옥을 탈출할 수 있다.

그래서 컨테이너 환경에서는 namespace와 함께 사용해 보안성을 높인다.


Linux namespace

프로세스 간 시스템 자원(e.g. PID, Mount, Network)을 격리하기 위한 Linux 커널 기능

  • namespace는 프로세스들이 공유하는 '해당' 자원의 범위를 제한해서 ‘가상화’처럼 보이게 한다.
  • 프로세스가 서로 다른 namespace에 속하면, 해당 자원에 대해서는 격리되어 각각의 프로세스가 서로 다른 환경에서 동작하는 것처럼 보이게 된다.
  • lsns -p <pid>/proc/<PID>/ns에서 프로세스의 namespace를 조회할 수 있다.
  • unshare: 특정 namespace를 현재 쉘 세션 혹은 새로운 프로세스에서 분리할 때 사용하는 명령어
    • sudo unshare --mount --uts --ipc --net --pid --fork --user --mount-proc bash
  • Mount(mnt) namespace: 프로세스 간 서로 다른 mount point 가지도록 한다.
    • Mount: Unix 계열 시스템에서 외부 파일 시스템(또는 장치)을 기존의 디렉토리 트리에 연결하여 접근할 수 있도록 하는 작업.
      • 이때 연결되는 디렉토리를 ‘마운트 지점(mount point)’이라고 한다.
    • OverlayFS: Union mount 기법의 일종, 두 개 이상의 디렉토리를 하나의 통합된 파일 시스템처럼 보이게 해주는 리눅스 커널 파일 시스템
      • 일반적으로 읽기 전용 계층(lowerdir)과 쓰기 가능한 계층(upperdir)을 겹쳐서 하나의 가상 파일시스템을 구성한다.
    • OverlayFS는 도커 이미지처럼 읽기 전용(base image) + 쓰기 계층(container layer) 방식으로 격리를 구현할 수 있게 도와준다.
    • Mount namespace + Overlayfs로 프로세스 간 mount를 분리하고 읽기 전용과 읽기-쓰기 계층을 나눠서 관리가 가능한다.
  • PID namespace: 동일한 프로세스에 대해, 각 네임스페이스에서 서로 다른 PID를 갖도록 한다.
    • 서로 다른 프로세스 트리처럼 보여서 다른 PID namespace에 있는 두 프로세스는 서로를 볼 수 없다.
  • IPC(Inter-Process Communication) namespace: System V와 일부 Posix 기반의 프로세스 간 통신을 격리한다.
    • 공유 메모리(프로세스 간 빠른 데이터 공유를 위한 메모리 공간), 세마포어(여러 프로세스 간의 동기화 제어 수단), 메세지 큐(커널을 통한 메시지 기반 통신 수단) 등의 통신 자원을 격리한다.
    • 컨테이너별로 통신 자원이 노출되지 않고 충돌하지 않아 완전한 가상 환경처럼 구성이 가능하다.
  • Network namespace: 하나의 물리적 네트워크 스택을 여러 가상 네트워크 스택으로 분리하여 사용할 수 있게 해준다.
    • 같은 호스트에서 실행 중인 여러 프로세스들이 각자 자신만의 가상 네트워크 환경을 가지게 만든다.
    • 각각의 네임스페이스가 서로 독립된 네트워크 인터페이스, IP 주소, 라우팅 테이블, iptabels 방화벽 규칙 등을 가질 수 있다.
    • 각 컨테이너에 독립된 네트워크 네임스페이스를 할당하여 네트워크 트래픽을 분리한다.
    • 두 namespace간 연결이 필요할 때는 일반적으로 veth pair를 사용하고, 브릿지(bridge), NAT를 통해 외부 인터넷과 연결할 수도 있다.
      • bridge에 연결되어 하나의 세그먼트처럼 동작한다.
    • Bridge는 Docker의 기본 네트워크이며 L2 계층의 데이터 링크 장치로 프레임을 필터링해 세그먼트 간 전달한다.
    • 하나의 Bridge는 여러 컨테이너를 연결하며, default bridge 외에 Custom bridge도 생성 가능하고, CNI를 통해 연결된다.
  • UTS(Unix Time-Sharing) namespace: 컴퓨터 자원을 공유하면서 사용자 간 분리를 위해 시스템 식별 정보인 hostnamedomainname을 격리한다.
    • 원래 hostname은 커널 전역(global)이기 때문에, 하나의 시스템에서 변경하면 전체에 영향을 준다.
    • UTS namespace를 사용하면 프로세스 단위로 서로 다른 hostname/domainname을 가질 수 있다.
    • 컨테이너 환경에서 시스템 정체성을 분리해서 개별 가상 머신처럼 보이게 만들 수 있다.
  • User ID namespace: 호스트 시스템의 사용자 ID(UID)와 그룹 ID(GID)를 격리할 수 있게 해주는 기능
    • 컨테이너 안에서 UID 0(root)으로 동작하더라도, 호스트에서는 0이 아닌 UID로 매핑될 수 있다.
    • 컨테이너 안에서 root로 실행되는 프로세스가 있다고 해도,
      이게 호스트에서는 일반 사용자로 보이게 할 수 있어서 보안적으로 안전하다.
    • Kubernetes나 Docker container는 일반적으로 격리하지 않는다.
      • PID, Network namespace 공유 기능에 문제가 생긴다.
      • 호스트의 디렉토리를 마운트했을 때, UID 매핑이 다르면 컨테이너 안에서 접근 권한 문제가 생긴다.
    • 하지만, 일반 사용자가 root 권한 없이도 컨테이너를 실행할 수 있도록 하는 기술인 rootless container를 위해서는 격리가 필수적이다.
    • 사용하지 않을거면 신뢰할 수 있는 사용자만 컨테이너 런타임을 실행할 수 있도록 하고, 컨테이너의 프로세스가 root user로 실행되지 않도록 해야 한다.
    • 호스트의 디렉토리를 컨테이너가 직접 접근할 수 있도록 마운트해서도 안된다.

cgroup (Control group)

프로세스 그룹에 대해 CPU, 메모리, 네트워크, 디스크 등의 시스템 자원을 제한하거나 우선순위를 조정하거나 모니터링할 수 있게 해주는 기능

  • 자원 제한, 자원 분리, 자원 모니터링 등을 하기 위해 각 자원 별로 controller가 존재한다.
  • 컨테이너에 자원 제한할 때 내부적으로 cgroup을 사용한다.

Docker의 네트워크

Docker는 컨테이너 간의 통신을 관리하고 격리하기 위해 논리적 네트워크 기능을 제공한다. 이는 같은 호스트 내에서 실행 중인 컨테이너 간의 연결을 가능하게 하며, 컨테이너 생성 시 자동으로 veth(Virtual Ethernet) 인터페이스 쌍을 만들어 컨테이너와 호스트를 연결한다.

Docker 네트워크 드라이버

Docker는 Native 드라이버와 Remote(3rd Party) 드라이버를 지원한다.

  • Docker Native 네트워크 드라이버 종류
    • 네트워크를 어떻게 구성하고 연결할지를 정의하는 구성 방식
    • Bridge: 기본 네트워크로, 컨테이너 포트를 외부에 노출하여 통신.
    • Host: 컨테이너가 호스트의 네트워크 공간을 직접 사용하는 방식으로, 포트 바인딩 불필요.
    • None: 네트워크 기능이 없는 상태로 커스텀 네트워크 구성 시 사용.
    • Overlay: 다중 호스트 간 통신을 위한 논리적 네트워크.

Docker 네트워크 구조

컨테이너의 네트워크 설정 옵션은 여러가지가 있지만, 보통 Bridge Network (docker0)를 사용한다.

  • Internal private network로, 도커가 호스트에 설치될 때, Default로 생성(172.17.0.x 대역의 주소)되는 가상 브릿지 네트워크 인터페이스이다.
  • 컨테이너들의 내부 네트워크 허브 역할을 해 컨테이너는 이 브릿지를 통해 서로 통신하고, 외부로 NAT를 통해 나갈 수 있다.
  • 컨테이너가 생성될 때마다, Docker는 먼저 해당 컨테이너를 위한 Network namespace를 만든다.
  • 네임스페이스 간 통신을 위한 Virtual cable인 veth Pair를 만들어 하나는 컨테이너의 network namespace 쪽에(eth0@XXXX), 다른 하나는 host의 bridge 쪽에(vethXXXX) 붙는다.
  • 각각을 컨테이너와 docker0라는 Docker bridge 네트워크에 연결한다.
  • 추가 옵션으로 none 옵션을 줘서 서로 접근이 불가능한 컨테이너를 만들거나, host 옵션으로 컨테이너가 도커 호스트의 네트워크에 분리되지 않고 연결되도록 할 수 있다.
  • Bridge network로 서로 다른 namespace의 컨테이너들을 연결하는 절차
    1. Create Network Namespace: 컨테이너마다 독립적인 네트워크 환경 생성
    2. Create Bridge Network/Interface: 호스트에 docker0 브릿지 생성
    3. Create vEth Pairs: 가상 케이블을 구성할 두 개의 연결된 인터페이스 생성
    4. Attach vEth to Namespace: 한쪽(eth0)은 컨테이너 네임스페이스에 이동
    5. Attach Other vEth to Bridge: 반대쪽(vethXXXX)은 docker0에 연결
    6. Assign IP Addresses: 각 인터페이스에 IP 할당
    7. Bring the interfaces up: 인터페이스 활성화
    8. Enable NAT - IP Masquerade: 외부 통신을 위해 NAT 설정 (iptables로 구현)
profile
It’s always white night here.

0개의 댓글