티스토리 뷰

쿠버네티스 관련 공부를 하며 파드에 대한 기본 요소를 설명한 부분을 봤는데, 꽤나 흥미로운 주제라 이번 기회에 리눅스/컨테이너에 대해 공부하는 겸 리눅스 기능을 활용해 컨테이너를 만들어보려고 한다.

 

저도 그렇습니다 🤓

 

 

1. 개요

작업 순서

 

  1. 실습 환경 구축
  2. root 디렉토리 변경
  3. 데이터 제공 및 컨테이너 데이터 영속화
  4. 프로세스 격리
  5. 네트워크 격리
  6. 리소스 제한

 

주사용 기능

 

chroot

  • Change Root Directory
  • 현재 실행 중인 프로세스와 차일드 프로세스 그룹에서 루트 디렉토리를 변경한다.
  • 이렇게 수정된 환경에서 실행되는 프로그램은 지정된 디렉토리 트리 밖의 파일들에 일반적으로는 접근이 불가능한다.

 

mount

  • 파일 시스템을 지정된 위치(마운트 위치)에서 사용 가능하게 만든다.

 

unshare

  • 새로운 네임스페이스에서 프로그램을 실행한다.

 

cgroup

  • Control Groups
  • 프로세스들의 리소스 사용(cpu, memory, disk i/o, network 등)을 제한하고 격리시킨다.

 

 

2. 실습 환경 구축

이번 포스팅의 목적은 특정 os에서 발생하는 문제를 트러블 슈팅하는게 아니므로, 실습을 하기 편한 환경을 선택했다.

사족을 달자면, docker container로 실습 환경을 구축하려고 했지만, proc 디렉토리를 마운트할 때 permission 이슈로 실습이 의도된대로 되지 않아 multipass로 vm을 생성했다. 그리고 ubuntu 22.04.6 LTS로 실습을 진행하다가, cgroup 설정 관련 이슈로 20.04를 선택했다.

 

 

command

# 20.04.6 LTS
multipass launch 20.04 --name test-instance

 

3. chroot를 사용해 루트 디렉토리 변경

목표

 

  • 지정된 디렉토리 밖의 파일에는 접근하지 못하도록 루트 디렉토리를 변경한다.

 

 

작업 순서

 

  1. 실행할 프로그램과 프로그램이 실행돼야 하는 파일 시스템의 위치 결정한다.
  2. 프로세스가 실행될 환경을 생성한다. → bash 같이 필요한 리눅스 프로그램을 새로운 루트로 불러온다.
  3. 실행하고 싶은 프로그램을 chroot 처리된 위치로 복사한다.

 

 

command

 

container=/tmp/container
mkdir -p $container/{bin,lib,proc}

cp /usr/bin/{kill,ps} $container/bin
cp /bin/{bash,ls} $container/bin
cp -r /lib/* $container/lib

sudo mount -t proc proc $container/proc
sudo chroot $container /bin/bash

 

 

결과

 

루트 디렉토리에서에서 ls 명령어를 사용하면, 컨테이너 내에 있는 파일만 노출되는 것을 볼 수 있다.

 

 

4. mount를 활용해 데이터 제공 및 컨테이너 데이터 영속화

목표

 

  • 생성한 컨테이너로 특정 데이터를 가져온다.
  • 컨테이너 데이터를 영속화한다.

 

command

 

container=/tmp/container
mkdir -p $container/{bin,lib,proc,data}          # data 디렉토리 추가

cp /usr/bin/{kill,ps} $container/bin
cp /bin/{bash,ls} $container/bin
cp -r /lib/* $container/lib

sudo mount -t proc proc $container/proc
mkdir /tmp/test-data                             # 테스트 데이터 추가
touch /tmp/test-data/{dummy1,dummy2}             # 테스트 데이터 추가
sudo mount --bind /tmp/test-data $container/data # mount
sudo chroot $container /bin/bash

 

 

결과

 

도커 볼륨과 같이 컨테이너 내에 데이터를 제공하거나, 컨테이너 내에 있는 데이터를 영속화할 수 있게 되었다.

 

 

 

✅ 참고: 컨테이너 삭제

 

컨테이너에 기능을 점진적으로 추가하는 과정에서 컨테이너를 삭제하고 다시 만들게 되는데, 아래와 같이 컨테이너를 삭제하는 스크립트를 만들어서 사용했다.

#!/bin/bash

sudo umount /tmp/container/proc
sudo umount /tmp/container/data
rm -rf /tmp/container
rm -rf /tmp/test-data

 

 

 

5. unshare를 활용해 프로세스 격리

이전 과정을 통해 생성한 컨테이너의 경우, chroot 커맨드를 사용해 루트로 지정된 디렉토리 밖의 디렉토리에 접근 할 수는 없지만 다른 프로세스를 kill 할 수 있다. 이것이 실제 컨테이너라면 전체 클러스터를 중단시킬 수 있는 취약점이 될 수 있다.

 

 

취약점 sample

 

분할된 화면 중 위에 있는 것이 chroot 처리된 프로세스인데, 해당 프로세스에서 외부에 있는 프로세스(아래에 있는 분할 화면인 vi 프로세스)를 kill을 할 수 있다.

 

 

 

목표

 

  • 컨테이너화된 운영 환경을 구축할 때 우선적으로 해결해야 하는 문제 중 하나가 격리(isolation)이다.
  • 이를 위해 unshare 명령어를 사용해 chroot-bash를 프로세스 공간이 완전히 격리된 터미널에서 실행한다.
  • 즉, 고립된 프로세스를 만든다.

 

 

현재 상태

 

이전에 생성한 컨테이너에서 ps -ax 커맨드를 통해 프로세스를 조회할 경우, 컨테이너 외부의 프로세스를 조회할 수 있는 것을 확인할 수 있다.

 

 

 

 

command

 

unshare을 통해 PID 네임스페이스를 만든다.

 

container=/tmp/container
mkdir -p $container/{bin,lib,proc,data}

cp /usr/bin/{kill,ps} $container/bin
cp /bin/{bash,ls} $container/bin
cp -r /lib/* $container/lib

sudo mount -t proc proc $container/proc
mkdir /tmp/test-data                             
touch /tmp/test-data/{dummy1,dummy2}             
sudo mount --bind /tmp/test-data $container/data 

# unshare 커맨드 추가
sudo unshare -fp --mount-proc=$container/proc chroot /tmp/container /bin/bash

 

 

 

결과

 

ps -ax 커맨드를 통해 새로운 네임스페이스에서 프로세스가 실행됐음을 확인할 수 있다.

 

 

 

6. unshare를 활용해 네트워크 격리

이전 과정을 통해 프로세스를 다른 프로세스들과 격리했지만, 여전히 같은 네트워크를 사용한다.

 

 

현재 상태

 

컨테이너(위 분할 화면)와 컨테이너 프로세스를 띄운 인스턴스(아래 분할 화면)가 동일한 네트워크 설정을 사용하는 것을 알 수 있다.

 

 

 

목표

 

  • unshare 명령어를 통해 네트워크를 격리한다.

 

 

command

 

unshare -n 옵션을 통해 네트워크 네임스페이스를 만든다.

 

container=/tmp/container
mkdir -p $container/{bin,lib,proc,data}

cp /usr/bin/{kill,ps,ip,curl} $container/bin # ip, curl 커맨드 추가
cp /bin/{bash,ls} $container/bin
cp -r /lib/* $container/lib

sudo mount -t proc proc $container/proc
mkdir /tmp/test-data                             
touch /tmp/test-data/{dummy1,dummy2}             
sudo mount --bind /tmp/test-data $container/data 

# unshare 커맨드의 n 옵션 추가
sudo unshare -fpn --mount-proc=$container/proc chroot /tmp/container /bin/bash

 

 

 

결과

 

컨테이너 내 별도의 네트워크 네임스페이스가 생성된 것을 확인할 수 있다.

 

 

 

7. cgroup을 활용해 리소스 제한

목표

 

  • cgroup를 통해 컨테이너의 리소스를 제한한다.

 

 

command

 

컨테이너 생성

container=/tmp/container
mkdir -p $container/{bin,lib,proc,data}

cp /usr/bin/{kill,ps,ip,curl,stress} $container/bin # stress 커맨드 추가
cp /bin/{bash,ls} $container/bin
cp -r /lib/* $container/lib

sudo mount -t proc proc $container/proc
mkdir /tmp/test-data                             
touch /tmp/test-data/{dummy1,dummy2}             
sudo mount --bind /tmp/test-data $container/data 

sudo unshare -fpn --mount-proc=$container/proc chroot /tmp/container /bin/bash

 

 

컨테이너 외부에서 컨테이너의 리소스 제한. 예시에서는 cpu를 100ms(cpu.cfs_period_us) 주기로 30ms(cpu.cfs_quota_us) 동안 실행할 수 있도록 설정했다. 즉, 0.3cpu를 사용한다.

 

sudo cgcreate -g cpu:container
sudo cgset -r cpu.cfs_quota_us=30000 container # cpu.cfs_period_us=100000
sudo sh -c "echo {컨테이너 bash pid} > /sys/fs/cgroup/cpu/container/tasks"

 

 

 

 

결과

 

리소스 제한 전

 

 

리소스 제한 후에는 cpu를 적게 사용하는 것을 확인할 수 있다.

 

 

 


참고 자료 🙇‍♂️

댓글