개요
시스템의 6가지 네임스페이스 자원들을 격리하여, 아래처럼 시스템에 영향을 끼칠 수 있는 문제를 해결
•
컨테이너임에도 시스템의 루트 권한을 가짐
•
컨테이너가 호스트의 포트를 그대로 이용
•
컨테이너가 호스트의 프로세스를 포함
네임스페이스
특징
•
네임스페이스는 프로세스에 격리된 환경을 커널의 기능으로 제공
•
자식 프로세스는 부모 프로세스의 네임스페이스를 그대로 상속
•
모든 프로세스는 타입 별 네임스페이스가 존재 → /proc/<pid>/ns
종류
•
mount
•
uts
•
ipc
•
net
•
pid
•
user
•
cgroup
•
time
관련 명령어
# 현재 프로세스의 pid
echo $$
# 현재 프로세스의 네임스페이스 조회
ls -al /proc/$$/ns
# 심볼릭 링크 조회
readlink /proc/$$/ns/<type>
# https://man7.org/linux/man-pages/man8/lsns.8.html
# pid와 타입으로 네임스페이스 목록 조회
lsns -t <type> -p <pid>
Shell
복사
격리
mount
# 마운트 네임스페이스 격리
unshare -m
# 컨테이너 환경의 네임스페이스 확인
lsns -p $$
# 격리 환경 종료
exit
# 호스트 환경의 네임스페이스 확인
lsns -p $$
Shell
복사
격리된 마운트 네임스페이스만 pid가 다르고, 나머지 네임스페이스는 1번 프로세스의 네임스페이스를 이용
uts
# uts 네임스페이스 격리
unshare -u
# 컨테이너 환경의 네임스페이스 확인
lsns -p $$
# 격리 환경 종료
exit
# 호스트 환경의 네임스페이스 확인
lsns -p $$
Shell
복사
격리된 uts 네임스페이스만 pid가 다르고, 나머지 네임스페이스는 1번 프로세스의 네임스페이스를 이용
ipc
# ipc 네임스페이스 격리
unshare -i
# 컨테이너 환경의 네임스페이스 확인
lsns -p $$
# 격리 환경 종료
exit
# 호스트 환경의 네임스페이스 확인
lsns -p $$
Shell
복사
격리된 ipc 네임스페이스만 pid가 다르고, 나머지 네임스페이스는 1번 프로세스의 네임스페이스를 이용
pid
•
호스트 네임스페이스에서는 컨테이너 네임스페이스의 pid 조회 가능
•
컨테이너 네임스페이스는 컨테이너 환경에서 격리된 pid가 있고, 외부적으로 호스트에서 사용하는 pid가 존재
PID 1
pid 0번은 커널 프로세스 (커널 레이어)인데 커널이 만든 pid 1번 프로세스가 최상위 프로세스 (유저 레이어)가 되고, 나머지 프로세스들일 1번의 자식 프로세스
동작 방식
pid의 격리는 다른 네임스페이스들과 달리 unshare 호출 시점에 즉시 격리되는 것이 아니라, fork 과정을 거치게 됨
unshare의 인자로 실행할 명령어를 기입할 수 있었는데, -f 옵션을 통해 unshare에서 해당 명령어로의 fork가 이뤄지면서 격리된 네임스페이스에 1번 pid를 차지하게 됨
격리된 1번 프로세스는 시그널이나 좀비 및 고아 프로세스 처리를 하고, 해당 프로세스가 죽으면 컨테이너를 종료하는 식으로 동작
1번 프로세스는 컨테이너의 라이프사이클과 밀접한 관계
격리 확인
# pid 격리
# -f는 fork
# -p는 pid
# --mount-proc은 proc 파일 시스템을 마운트
unshare -fp --mount-proc /bin/sh
# 컨테이너 내외부에서 확인
ps -ef | grep /bin/sh
# 컨테이너 내부에서의 네임스페이스 확인
lsns -t pid -p 1
# 호스트에서의 네임스페이스 확인
lsns -t pid -p <container-pid>
# 호스트에서 container를 죽이면, 격리 공간이 사라짐
kill -9 <container-pid>
Shell
복사
net
•
네트워크 네임스페이스를 생성하여 가상 인터페이스를 이용
•
네임스페이스를 삭제 시 가상 인터페이스도 삭제
네트워크 셋업
# veth0, veth1을 양 끝으로 갖는 veth 생성
ip link add veth0 type veth peer name veth1
# ip link로 생성된 veth 인터페이스를 확인 (veth1@veth0 / veth0@veth1)
ip link
# RED, BLUE 네임스페이스 생성
ip netns add RED
ip netns add BLUE
# RED-veth0 / BLUE-veth1 연결
ip link set veth0 netns RED
ip link set veth1 netns BLUE
# RED, BLUE 네임스페이스의 장치 활성화
ip netns exec RED ip link set veth0 up
ip netns exec BLUE ip link set veth1 up
# RED, BLUE 네임스페이스의 IP 주소 추가
ip netns exec RED ip addr add 11.11.11.2/24 dev veth0
ip netns exec BLUE ip addr add 11.11.11.3/24 dev veth1
Shell
복사
네임스페이스 생성 확인
tree /var/run/netns
Shell
복사
네트워크 네임스페이스 생성 시 /var/run/netns에 파일이 생성
통신 확인
# 각각의 터미널에서 네트워크 네임스페이스 접속
nsenter --net=/var/run/netns/<namespace>
# 네트워크 inode 값 상이함을 확인
lsns -t net -p $$
# 각각의 IP 주소 및 인터페이스 확인
ip a
# 라우팅 테이블을 확인하여 각자의 IP 주소로부터 디폴트로 나가는 것을 확인
ip route
# 다른 네임스페이스로 핑을 확인
ping <other-namespace-ip-address>
Shell
복사
좌측 RED 접속, 우측 BLUE 접속하여 통신 테스트
네임스페이스 및 링크 삭제
# 네임스페이스 삭제
ip netns del RED
ip netns del BLUE
# netns 삭제 확인
tree /var/run/netns
# 링크 삭제 확인 (네임스페이스 삭제 시 자동 정리)
ip link del veth0
Shell
복사
user
•
UID/GID를 Remap으로 격리
•
컨테이너가 시스템의 루트 권한을 갖는 문제 해결
도커로 확인
# for docker) 실행 중인 프로세스 목록에서 /bin/sh가 존재하지 않음 확인
ps -ef | grep /bin/sh
# for docker) ubuntu 사용자로 ubuntu 컨테이너 생성 및 접속
docker run -it ubuntu /bin/sh
# for docker) 컨테이너에서 사용자 확인, root
id
# for ubuntu) 호스트에서 사용자 확인, ubuntu
id
# for ubuntu) 실행 중인 프로세스 목록에서 호스트와 컨테이너 각각의 /bin/sh 존재 확인
# 같은 프로세스지만 격리되어 2개로 표시
# 각 프로세스의 사용자가 다른데 컨테이너에서 실행된 root가 시스템의 루트인지 확인 필요
ps -ef | grep /bin/sh
# for docker) 네임스페이스의 심볼릭 링크를 통해 네임스페이스 inode 확인
readlink /proc/$$/ns/user
# for ubuntu) 네임스페이스의 심볼릭 링크를 통해 네임스페이스 inode 확인
readlink /proc/$$/ns/user
Shell
복사
도커 컨테이너에서 실행된 root라는 사용자가 시스템의 root와 동일한 사용자라는 사실을 알 수 있음 (따라서 UID/GID Remap이 되도록 별도 설정이 필요)
격리 확인
# for unshare) 실행 중인 프로세스 목록에서 /bin/sh가 존재하지 않음 확인
ps -ef | grep /bin/sh
# for unshare) 사용자 격리
# --map-root-user는 --map-user=0 --map-group=0와 동일
unshare -U --map-root-user /bin/sh
# for unshare) 컨테이너에서 사용자 확인, root
id
# for ubuntu) 호스트에서 사용자 확인, ubuntu
id
# for ubuntu) 실행 중인 프로세스 목록에서 /bin/sh 존재 확인
# docker와 달리 pid 격리되지 않아 1개로 보임
ps -ef | grep /bin/sh
# for unshare) 네임스페이스의 심볼릭 링크를 통해 네임스페이스 inode 확인
readlink /proc/$$/ns/user
# for ubuntu) 네임스페이스의 심볼릭 링크를 통해 네임스페이스 inode 확인
readlink /proc/$$/ns/user
Shell
복사
도커 컨테이너로 실행한 것과 다르게 사용자 네임스페이스의 inode 값이 다름 (컨테이너 안에서의 root는 UID/GID의 Remap으로 시스템 상에서의 root와 다름)
cgroup
•
컨테이너 별로 자원을 분배하여 정해진 만큼의 기준으로 운용
•
v1은 request 및 limit으로만 동작하지만, v2는 qos 지정 가능
•
자원의 할당과 제한 등 관리 기능을 파일 시스템으로 제공 (/sys/fs/cgroup)
패키지 설치
# 루트 사용자 전환
sudo -i
# cgroup 관련 도구와 스트레스 도구 설치
apt install -y cgroup-tools stress
Shell
복사
cgroup 생성
# https://linux.die.net/man/1/cgcreate
# https://docs.redhat.com/ko/documentation/red_hat_enterprise_linux/6/html/resource_management_guide/sec-creating_cgroups#sec-Creating_Cgroups
# cgcreate -a <owner> -g <subsystem>:<path>
cgcreate -a root -g cpu:jseo
# 컨트롤 그룹 조회
tree /sys/fs/cgroup/jseo
Shell
복사
자원 제한 및 스트레스
# https://linux.die.net/man/1/cgset
# cpu 사용률 42% 제한
cgset -r cpu.max=42000 jseo
# https://linux.die.net/man/1/cgexec
# 컨트롤 그룩으로 stress 실행
cgexec -g cpu:jseo stress -c 1
# 다른 터미널에서 cpu 부하 확인
htop
Shell
복사
결론
컨테이너의 리소스를 격리하는 방식을 탐구했는데, 이전에 실습한 루트 디렉토리 격리와 루트 디렉토리를 오버레이 파일 시스템으로 구성하는 것까지 종합하여 도커 없이 컨테이너를 구성할 수 있음