WellSpring

1주차 : 컨테이너 격리 & 네트워크 및 보안 본문

KANS3 - k8s Advanced Networking Study

1주차 : 컨테이너 격리 & 네트워크 및 보안

daniel00324 2024. 8. 27. 16:39

※ 본 게재 글은 gasida님의 KANS(Kubernetes Advanced Networking Study) 강의내용과 실습예제 및 docker 공식 가이드 문서, "15단계로 배우는 도커와 쿠버네티스" 서적 등을 참고하여 작성하였습니다. 블로깅도, 모임학습도 처음인지라 제가 할 수 있는 최선의 방법으로 내용을 풀어가 보도록 하겠습니다~ ^^;;

 

1. 도커에 관하여

 

1.1 What is Docker?

  - 컨테이너 기술을 이용해 애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼 이다!!

  - 좀 더 정확히 이야기 하면, 컨테이너화 된 하나의 프로세스(containerized Process) 이고

  - 도커 플랫폼이 설치된 곳이라면 컨테이너로 묶인 애플리케이션을 어디서든 실행할 수 있음

 

 

  https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/

 

1.2 Docker 컨테이너 vs 가상머신

[ 컨테이너와 가상머신의 구조차이 ]

https://www.docker.com/resources/what-container

 

1) Docker 컨테이너

  • 호스트 운영체제의 커널을 공유하며, 가볍게 격리된 환경을 생성
  • 가상 머신보다 더 가벼우며 효율적으로 실행됨
  • 이미지와 컨테이너 레이어를 사용하여 빠르게 생성되며, 실행 속도가 매우 빠름
  • 호스트 운영체제의 커널을 공유하여 가볍고 효율적으로 자원을 활용 가능함

2) 가상머신

  • 호스트 운영체제 위에 가상화된 하드웨어 계층을 생성하며, 각 가상머신은 독립된 운영체제, 커널, 드라이버 등을 가짐
  • 상기 구조적 특성으로 인해 상대적으로 무겁고 자원소모가 많음
  • 운영체제의 부팅과정이 필요하여 실행속도가 상대적으로 느림
  • 독립된 운영체제를 가지므로 메모리, 디스크 공간 등 자원을 많이 소비함

[ Quiz ] 보안 측면에서 '가상 머신' 환경과 '컨테이너' 환경 중 좀 더 격리 수준이 높은쪽은 어디일까요?

[ Answer ]

더보기
  • 컨테이너 환경에서는 호스트 OS(커널)을 공유한다 → 약한 격리(Weak Isolation)
https://netpple.github.io/docs/make-container-without-docker/

 

> 가상 머신은 기존의 서버에 하이퍼바이저를 설치하고, 그 위에 가상 OS와 패키징한 VM을 만들어 실행하는 방식인 하드웨어 레벨의 가상화를 지원합니다. 

☞ 개별 VM은 독립된 OS를 사용하여 도커에 비해 고립성(보안)은 더 좋지만, 오버헤드가 크고 무겁고 느림

 

> 반면,  컨테이너는 운영체제를 제외한 나머지 애플리케이션 실행에 필요한 모든 파일을 패키징한다는 점에서 OS레벨 가상화를 지원합니다.

☞ 게스트OS 와 하이버파이저가 없기 때문에, 오버헤드를 줄임으로써 훨씬 더 가볍게 프로세스를 실행할 수 있고 컨테이너에 대한 복제와 배포가 더 용이함

 

 . 호스트의 커널을 공유하지만, 개별적인 '사용자 공간(user space)' 을 할당함

 . 가상화된 공간을 생성하기 위해 리눅스 기능 pivot-root, 네임스페이스(namespace), cgroup 를 사용함으로써 프로세스 단위의 격리 환경리소스을 제공

 

 

1.3 Benefits of Docker

  - 응용 프로그램을 서로 다른 환경에서도 일관되게 실행 할 수 있다

  - 개발 환경과 운영 환경 사이의 Gap으로 인한 문제를 줄일 수 있다

  - 가볍고 빠르며 확장성이 좋아서 개발 및 배포 프로세스를 간소화 할 수 있다

 

[ Tip & Tips ]

더보기

> 도커 이미지란?

  - 서비스 운영에 필요한 서버 프로그램, 소스코드, 라이브러리, 컴파일된 실행 파일을 하나의 도커파일 (Dockerfile)로 묶어 빌드 한 것

 

> 도커 파일 (Dockerfile) 이란? 
- docker에서 이용하는 이미지를 기반으로 스크립트를 통해 customizing 한 나만의 이미지 파일

 

 

1.4 Docker Architecture ( Docker 는 기본적으로 unix domain socket 통신을 한다!! )

https://docs.docker.com/get-started/docker-overview/#docker-architecture

 

https://docs.docker.com/get-started/overview/#docker-architecture

 

1.5 프로세스 기초이해

 > Linux Process 의 이해  - Blog

더보기

 [ 가상화된 자원을 사용하는 프로세스의 구조 ]

( 영상 출처 : https://youtu.be/xewZYX1e5R8?si=x8lF0HOdR5-CUmfS&t=891 )
  • 프로세스실행 중인 프로그램의 인스턴스를 의미. OS에서 프로세스를 관리하며, 각 프로세스는 고유한 ID(PID)를 가짐
  • 프로세스는 CPU와 메모리를 사용하는 기본 단위로, OS 커널(Cgroup)에서 각 프로세스의 자원을 관리함
# 프로세스 정보 확인
ps

# /sbin/init 1번 프로세스 확인
# 프로세스별 CPU 차지율, Memory 점유율, 실제 메모리 사용량 등 확인 >> 비율로 표현되는 이유는?
ps aux
ps -ef

# 프로세스 트리 확인
pstree --help
pstree
pstree -a
pstree -p
pstree -apn
pstree -apnT
pstree -apnTZ
pstree -apnTZ | grep -v unconfined

# 실시간 프로세스 정보 출력
top -d 1
htop

# 특정 프로세스 정보 찾기
pgrep -h

# [터미널1]
sleep 10000

# [터미널2]
pgrep sleep
pgrep sleep -u root
pgrep sleep -u ubuntu
https://twitter.com/b0rk/status/981159808832286720/photo/1

 

[ proc 에 대한 이해 ]

#
mount -t proc
findmnt /proc
TARGET SOURCE FSTYPE OPTIONS
/proc  proc   proc   rw,nosuid,nodev,noexec,relatime

#
ls /proc
tree /proc -L 1
tree /proc -L 1 | more

# 커널이 동적으로 생성하는 정보
cat /proc/cpuinfo
cat /proc/meminfo
cat /proc/uptime
cat /proc/loadavg
cat /proc/version
cat /proc/filesystems
cat /proc/partitions

# 실시간(갱신) 정보
cat /proc/uptime
cat /proc/uptime
cat /proc/uptime

# 프로세스별 정보
ls /proc > 1.txt

# [터미널1]
sleep 10000

# [터미널2]
## 프로세스별 정보
ls /proc > 2.txt
ls /proc
diff 1.txt 2.txt
pstree -p
ps -C sleep
pgrep sleep

## sleep 프로세스 디렉터리 확인
tree /proc/$(pgrep sleep) -L 1
tree /proc/$(pgrep sleep) -L 2 | more

cat /proc/$(pgrep sleep)/cmdline ; echo
ls -l /proc/$(pgrep sleep)/cwd
ls -l /proc/$(pgrep sleep)/exe
cat /proc/$(pgrep sleep)/environ ; echo
cat /proc/$(pgrep sleep)/maps
cat /proc/$(pgrep sleep)/stat
cat /proc/$(pgrep sleep)/status

 

[ 터미널#1 에서 수행한 sleep 프로세스에 대해 터미널#2 에서 확인한 결과 ]

 


[ 실습환경 구성 ]

1) 사전준비 사항 : AWS 계정, SSH 키 페어, AWS Cli 설치 및 자격증명 설정

2) 구성내역 : VPC 1개(퍼블릭 서브넷 2개), EC 인스턴스 1대(Ubuntu 22.04 LTS, t3.small)

  • CloudFormation 스택 실행 시 파라미터를 기입하면, 해당 정보가 반영되어 배포됩니다.
  • CloudFormation 에 EC2의 UserData 부분(Script 실행)으로 실습 환경에 필요한 기본 설정들이 자동으로 진행됩니다.
더보기
# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/kans/kans-1w.yaml

# CloudFormation 스택 배포
# aws cloudformation deploy --template-file kans-1w.yaml --stack-name mylab --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region ap-northeast-2
예시) aws cloudformation deploy --template-file kans-1w.yaml --stack-name mylab --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

## Tip. 인스턴스 타입 변경 : MyInstanceType=t2.micro
예시) aws cloudformation deploy --template-file kans-1w.yaml --stack-name mylab --parameter-overrides MyInstanceType=t2.micro KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2

# [모니터링] CloudFormation 스택 상태 : 생성 완료 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

# Ansible Server EC2 SSH 접속
ssh -i ~/.ssh/kp-gasida.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)

3) 기본 환경접속 및 테스트

 > Ubuntu EC2 SSH 접속 : 기본 정보 확인 ← 스택 생성 시작 후 1분 후 접속 할 것

더보기
# SSH 접속 : ubuntu 계정
ssh -i ~/.ssh/kp-gasida.pem ubuntu@$(aws cloudformation describe-stacks --stack-name mylab --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
------------------------
# 현재 디렉터리 확인
pwd

# 계정 정보 확인
whoami
id

# 버전 확인
lsb_release -a

# CPU, Mem, Disk 등 기본 정보 확인
hostnamectl
lscpu
htop
free -h
df -hT /
mount
findmnt -A
cat /etc/hosts

# iptables 정책 확인
sudo iptables -t filter -L
sudo iptables -t nat -L

# 네트워크 정보 확인 - 리눅스아재력테스트
ip -br -c addr show
ip a(=ip addr) 혹은 ifconfig
ip route 혹은 route -n

# 프로세스 확인 - 셸변수
ps auf
pstree -p
echo $$
kill -9 $$

 

[ 실행결과 - iptables 비교 ]

더보기

1) Docker 설치 전

2) Docker 설치 후

 


[ Docker 설치 및 확인 ]

 

1) Docker 설치 - 링크 script

더보기
# [터미널1] 관리자 전환
sudo su -
whoami
id

# 도커 설치
curl -fsSL https://get.docker.com | sh

# 도커 정보 확인 : Client 와 Server , Storage Driver(overlay2), Cgroup Version(2), Default Runtime(runc)
docker info
docker version

# 도커 서비스 상태 확인
systemctl status docker -l --no-pager

# 모든 서비스의 상태 표시 - 링크
systemctl list-units --type=service

# 도커 루트 디렉터리 확인 : Docker Root Dir(/var/lib/docker)
tree -L 3 /var/lib/docker

 

[ 실행 화면 - docker 설치 및 Check ]

 

더보기

1) 설치

2) 상태확인

3) 모든 서비스 상태확인

 

4) Docker Root-dir. 확인


2) Manage Docker as a non-root user & Socket 소켓 - Link

 

[Warning] The docker group grants root-level privileges to the user. For details on how this impacts security in your system, see Docker Daemon Attack Surface.

  • The Docker daemon binds to a Unix socket, not a TCP port. By default it's the root user that owns the Unix socket, and other users can only access it using sudo.
  • The Docker daemon always runs as the root user

[ 참고 - 동영상 : 널널한 개발자 TV - Socket 0100 소켓 본질에 대한 이해 ]

☞ 소켓(Socket)은 OS 커널에 구현되어 있는 프로토콜 요소에 대한 추상화된 인터페이스, 장치 파일의 일종

더보기

 

# [터미널2] 일반 유저 ubuntu 로 실습 진행
whoami

# 도커 서버 정보 획득 실패
docker info
...
Server:
ERROR: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.46/info": dial unix /var/run/docker.sock: connect: permission denied
errors pretty printing info

#
ls -l /run/docker.sock /var/run/docker.sock
file /var/run/docker.sock

# [터미널1] 관리자
sudo su -

#
sudo systemctl status docker -l --no-pager
docker info

# 소켓 정보 확인 : tcp, udp, sctp, Unix Domain
ss -h | grep sockets
ss -tl # 혹은 ss --tcp --listening
ss -xl # 혹은 ss --unix --listening
ss -xl | grep -i docker
u_str LISTEN 0      4096                               /run/docker.sock 69239            * 0          
u_str LISTEN 0      4096                   /var/run/docker/metrics.sock 69882            * 0          
u_str LISTEN 0      4096   /var/run/docker/libnetwork/914c2d2f1446.sock 69422            * 0 

# 특정 소켓 파일을 사용하는 프로세스 확인
lsof /run/docker.sock
COMMAND  PID USER   FD   TYPE             DEVICE SIZE/OFF  NODE NAME
systemd    1 root   40u  unix 0xffff96bd96236a80      0t0 69239 /run/docker.sock type=STREAM
dockerd 5178 root    4u  unix 0xffff96bd96236a80      0t0 69239 /run/docker.sock type=STREAM

# unix domain socket 중 docker 필터링
lsof -U | grep -i docker

 

[ 실행결과 - ss help result ]

 

 

2-1) Manage Docker as a non-root user (실습)

더보기
# [터미널2] 일반 유저 ubuntu 로 실습 진행
whoami

# Create the docker group : 도커 스크립트 생성 시 자동 생성되어 그룹 확인만 진행
sudo groupadd docker
getent group | tail -n 3

# Add your user to the docker group.
echo $USER
sudo usermod -aG docker $USER

# ssh logout
exit

# ssh 재접속 후 확인

#
docker info


# 컨테이너 실행
docker run hello-world

#
docker ps
docker ps -a
docker images

# 중지된 컨테이너 삭제
docker ps -aq
docker rm -f $(docker ps -aq)
docker ps -a

 

[ 실행결과 - ubuntu 유저에 docker group 권한부여, docker 관리하기 ]

 

[ Before ]

 

[ After ]

 

2-2) [심화] 컨테이너가 host의 docker socket file 공유로 도커 실행 - Link1 , Link2

 

[ 도전과제 1 ] 호스트의 Docker 데몬을 사용하는 파이프라인 구성 및 해당 동작이 가능한 원리

더보기

> DinD(Docker In Docker)기술이란?

  : 컨테이너 내부에 Docker를 사용하는 기술을 일컫음

 

> DinD 원리

docker 설치 및 실행 시, "docker CLI" 와 "docker daemon" 이 설치 및 실행됨

콘솔 상에서 docker command 수행 시, docker CLI가 socket (/var/run/docker.sock)을 통해 docker daemon에게 명령어 전달

∴ 외부 docker CLI 가 /var/run/docker.sock 에 접근할 수 만 있다면, 내부 docker daemon에 명령을 수행시킬 수 있다!!!

 

 [ 실습 예제  ]

1. Docker 생성 시, local socket file 바인드 해주어서 Docker 내부에서 docker 생성 및 확인, 삭제 수행

#
docker run --rm -it -v /run/docker.sock:/run/docker.sock -v /usr/bin/docker:/usr/bin/docker ubuntu:latest bash
--------------------
docker info
docker run -d --rm --name webserver nginx:alpine
docker ps
docker rm -f webserver
docker ps -a
exit
--------------------

 [ docker run -v : 볼륨 마운트를 bind 해주는 옵션 ]

2. 도커로 Jenkins 서버 구축 시, local docker socket 파일 바인드 하여 명령어 수행 테스트 진행

# Jenkins 컨테이너 실행
docker run -d -p 8080:8080 -p 50000:50000 --name jenkins-server --restart=on-failure -v jenkins_home:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker jenkins/jenkins

# 확인
docker ps
docker volume ls
local     jenkins_home

# 초기 암호 확인
docker exec -it jenkins-server cat /var/jenkins_home/secrets/initialAdminPassword

# Jenkins 컨테이너 웹 접속 주소 확인 : 초기 암호 입력
echo "http://$(curl -s ipinfo.io/ip):8080"

# jdk 확인
docker exec -it jenkins-server java --version
# JAVA_HOME 확인
docker exec -it jenkins-server sh -c 'echo $JAVA_HOME'
# Git 확인
docker exec -it jenkins-server git -v

# 기본 사용자 확인
docker exec -it jenkins-server whoami

# Jenkins 컨테이너에서 도커 명령 실행
docker exec -it --user 0 jenkins-server whoami
docker exec -it --user 0 jenkins-server docker info
docker exec -it --user 0 jenkins-server docker run --rm hello-world
docker exec -it --user 0 jenkins-server docker ps

# Jenkins 컨테이너 삭제
docker rm -f jenkins-server
docker volume rm jenkins_home

 

[ 수행 결과 ]

[ jenkins docker server 생성 시, docker.sock 바인드 옵션 사용 ]
[ /var/run/docker.sock 파일 바인드를 통해 local에서 docker 내부로 명령어 전달 수행한 결과 ]

 


2. 컨네이너 격리 ( Namespace & cgoups )

 - 컨테이너는 Namespace 라는 격리공간을 통해 Host system의 자원을 가상화한 resource Pool 을 할당받고 다른 namespace 와 별개로 동작한다.

  • namespace는 프로세스가 볼 수 있는 범위를 지정한다
  • cgoups는  process가 사용 할 수 있는 자원의 사용량을 제한한다 

 

2-1. Namespace 란?

 - 리눅스 커널에 내장된 Resource-Isolation 기술을 일컫으며,

 - 개별 VM이 독립적인 공간을 가지고 있듯이 독립된 가상화 자원을 할당하는 범위를 일컫음

 - 현재 기준 8가지 namespace가 존재함

더보기
  • mnt (파일시스템 마운트): 호스트 파일시스템에 구애받지 않고 독립적으로 파일시스템을 마운트하거나
    언마운트 가능
  • pid (프로세스): 독립적인 프로세스 공간을 할당 
  • net (네트워크): namespace간에 network 충돌 방지 (중복 포트 바인딩 등)
  • ipc (SystemV IPC): 프로세스간의 독립적인 통신통로 할당
  • uts (Unix Time Sharing): 독립적인 hostname 할당
  • user (UID): 독립적인 사용자 할당, 외부 namespace와 다른 UID/GID 설정 가능 > 권한분리
  • cgroup : 해당 namesapce에 속한 프로세스가 사용할 수 있는 자원의 양을 제한
  • time : Process가 볼수 있는 system-time을 격리하여 특정 프로세스에 대해 다른 시간대 설정가능

 

https://wizardzines.com/comics/namespaces/

[ 리눅스 프로세스 격리 기술의 발전 ]

https://speakerdeck.com/kakao/ige-dwaeyo-dokeo-eobsi-keonteineo-mandeulgi?slide=200

 

2-2. cgroups (Control Groups)

 - 자원에 대한 제어를 가능하게 해주는 리눅스 커널의 기능

 - 관리 가능한 자원

  : CPU, Memory, I/O, Network, device 노드 

 

> 컨테이너에서는 프로세스를 디렉토리 구조에 파일 형태로 관리한다!!

   따라서, namespace에 해당하는 디렉토리 이하 파일을 수정해 주는 것에 의해 격리된 자원의 사용을 제한할 수 있다.

 

다음 실습을 통해 chroot + 탈옥/ pivot root 을 실습해 봄으로써, namespace의 격리구조에 대해 이해할 수 있다.

 

[ 실습 - 따라하기 ]

더보기

1. chroot + 탈옥 ( 참조 - (53) 이게 돼요? 도커 없이 컨테이너 만들기 / if(kakao)2022 - YouTube )

 

  •  chroot root directory : user 디렉터리를 user 프로세스에게 root 디렉터리를 속임
# [터미널1] 관리자 전환
sudo su -
whoami

#
cd /tmp
mkdir myroot

# chroot 사용법 : [옵션] NEWROOT [커맨드]
chroot myroot /bin/sh
chroot: failed to run command ‘/bin/sh’: No such file or directory

#
tree myroot
which sh
ldd /bin/sh

# 바이러리 파일과 라이브러리 파일 복사
mkdir -p myroot/bin
cp /usr/bin/sh myroot/bin/
mkdir -p myroot/{lib64,lib/x86_64-linux-gnu}
tree myroot

cp /lib/x86_64-linux-gnu/libc.so.6 myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot/

#
w
--------------------
ls
exit
--------------------

#
which ls
ldd /usr/bin/ls
	
#
cp /usr/bin/ls myroot/bin/
mkdir -p myroot/bin
cp /lib/x86_64-linux-gnu/{libselinux.so.1,libc.so.6,libpcre2-8.so.0} myroot/lib/x86_64-linux-gnu/
cp /lib64/ld-linux-x86-64.so.2 myroot/lib64
tree myroot

#
chroot myroot /bin/sh
--------------------
ls /

## 탈출 가능한지 시도
cd ../../../
ls /

# 아래 터미널2와 비교 후 빠져나오기
exit
--------------------
# chroot 요약 : 경로를 모으고(패키징), 경로에 가둬서 실행(격리)


# [터미널2]
# chroot 실행한 터미널1과 호스트 디렉터리 비교
ls /
  • chroot 에서 ps 실행해보기
# copy ps
ldd /usr/bin/ps;
cp /usr/bin/ps /tmp/myroot/bin/;
cp /lib/x86_64-linux-gnu/{libprocps.so.8,libc.so.6,libsystemd.so.0,liblzma.so.5,libgcrypt.so.20,libgpg-error.so.0,libzstd.so.1,libcap.so.2} /tmp/myroot/lib/x86_64-linux-gnu/;
mkdir -p /tmp/myroot/usr/lib/x86_64-linux-gnu;
cp /usr/lib/x86_64-linux-gnu/liblz4.so.1 /tmp/myroot/usr/lib/x86_64-linux-gnu/;
cp /lib64/ld-linux-x86-64.so.2 /tmp/myroot/lib64/;

# copy mount
ldd /usr/bin/mount;
cp /usr/bin/mount /tmp/myroot/bin/;
cp /lib/x86_64-linux-gnu/{libmount.so.1,libc.so.6,libblkid.so.1,libselinux.so.1,libpcre2-8.so.0} /tmp/myroot/lib/x86_64-linux-gnu/;
cp /lib64/ld-linux-x86-64.so.2 /tmp/myroot/lib64/;

# copy mkdir
ldd /usr/bin/mkdir;
cp /usr/bin/mkdir /tmp/myroot/bin/;
cp /lib/x86_64-linux-gnu/{libselinux.so.1,libc.so.6,libpcre2-8.so.0} /tmp/myroot/lib/x86_64-linux-gnu/;
cp /lib64/ld-linux-x86-64.so.2 /tmp/myroot/lib64/;

# tree 확인
tree myroot

#
chroot myroot /bin/sh
---------------------
# 왜 ps가 안될까요?
ps
Error, do this: mount -t proc proc /proc

#
mount -t proc proc /proc
mount: /proc: mount point does not exist.

#
mkdir /proc
mount -t proc proc /proc
mount -t proc

# ps는 /proc 의 실시간 정보를 활용
ps
ps auf
ps aux
ls -l /proc

exit
---------------------

# 실습 시 사용한 proc 마운트 제거
mount -t proc
sudo umount /tmp/myroot/proc
mount -t proc

[ chroot - ps 명령어 실습 ]

1) chroot 로 격리된 "myroot" 에서 처음에는 없는 명령어 수행 > 호스트로 빠져나와 해당 명령어 찾기

 

2) 명령어 chroot 이하 복사 후, 수행 점검

root@ip-172-16-1-47:/tmp# cp /usr/bin/ps
ps             psfaddtable    psfgettable    psfstriptable  psfxtable      pslog          pstree         pstree.x11
root@ip-172-16-1-47:/tmp# cp /usr/bin/ps myroot/bin/
root@ip-172-16-1-47:/tmp# cp /lib/x86_64-linux-gnu/{libproc2.so.0,libc.so.6,libsystemd.so.0,libcap.so.2,libgcrypt.so.20,liblz4.so.1,liblzma.so.5,libzstd.so.1,libgpg-error.so.0} myroot/lib/x86_64-linux-gnu/ ;
root@ip-172-16-1-47:/tmp# cp /lib64/ld-linux-x86-64.so.2 myroot/lib64/ ;

 

3) 추가 명령어 복사 ( 상기와 동일 과정 수행 : mkdir , mount ) 후, 재수행

 

 4) 드디어 성공  ^0^/  ->  [정리] exit 로 빠져나와 umount 수행

 > Easy-way to resolve 

wget https://raw.githubusercontent.com/sam0kim/container-internal/main/scripts/chroot_ps.sh;
bash chroot_ps.sh;

>> docker Image 패키징이란??? 필요한 명령어, 라이브러리 등을 모으고 압축해 놓은 것?!

 

"한 땀 한 땀 하기 힘드니, 남이 만들어 놓은 이미지를 사용해 쉽게 격리 환경 테스트를 해보자 !!"

  • 남이 만든 이미지 이용하여 chroot 해보기
#
mkdir nginx-root
tree nginx-root

# nginx 컨테이너 압축 이미지를 받아서 압축 풀기
docker export $(docker create nginx) | tar -C nginx-root -xvf -;
docker images

#
tree -L 1 nginx-root
tree -L 2 nginx-root | more

#
chroot nginx-root /bin/sh
---------------------
ls /

#
nginx -g "daemon off;"

# 터미널1에서 아래 확인 후 종료
CTRL +C # nginx 실행 종료
exit
---------------------

# [터미널2]
## 루트 디렉터리 비교 및 확인
ls /
ps -ef |grep nginx
curl localhost:80
sudo ss -tnlp

 

 > docker image를 다운로드 받아서 압축 풀어 놓은 상태

 

> nginx 구동 /중지 & Check !!

 

 


 

  •  탈옥 코드
#include <sys/stat.h>
#include <unistd.h>

int main(void)
{
  mkdir(".out", 0755);
  chroot(".out");
  chdir("../../../../../");
  chroot(".");

  return execl("/bin/sh", "-i", NULL);
}
  • 탈출 시도!!  Seccess ?? or  Not ??
# 컴파일
gcc -o myroot/escape_chroot escape_chroot.c
tree -L 1 myroot
file myroot/escape_chroot

# chroot 실행
chroot myroot /bin/sh
-----------------------
ls /
cd ../../
cd ../../
ls /

# 탈출!
./escape_chroot
ls /

# 종료
exit
exit
-----------------------

# [터미널2]
## 루트 디렉터리 비교 및 확인
ls /

> 탈옥코드 compile && excute !!

 

 

2-3. pivot root

 - chroot 의 탈옥 한계성으로 인해 이를 보안적인 관점에서 고민하게 되고 이에 대한 결과로 pivot root 가 나왔는데...

 - 그냥 '/' 에 적용하면 OS 운용에 필요한 참조 링크들이 다 꼬이는 문제가 발생!! ㅜ.ㅜ

 - 이런 문제를 해결하기 위해 'namespace' 라는 개념이 도입 되고, 'mount namespace'라는 격리 개념이 생겨났다.

 

. 마운트란(mount) ?

 - 루트 시스템에 부착되는 하위 포인트

 

[ More ... ]

더보기

1. pivot root : '/' 의 위치를 하위 임의의 격리된 위치로 변경 지정

 

2. 마운트 네임스페이스 : 부모(parent) mount namespace를 복사하여 자식(child) mount namespace를 만든다.

부모 프로세스는 'M'만 보이고, 자식 프로세스에서만 'X' 및 이하 마운트 구조가 보인다!!

 

∴ 부모 '/' 디렉토리를 마운트 할 수 있는 구조만 있다면, pivot root 마운트에 아무런 문제가 없다!!

 

[ 따라하기 - pivot root ]

 

☞ 'unshare' 명령어로 부모<>자식 간 namespace를 고립시키자!!

 

 

 



 

[ chroot 의 탈옥 취약점 해결한 컨테이너 파일시스템 격리구조 ]

 Mount 로 부모와 namespace 격리 후, Pivotroot 수행 ( 탈옥 불가 !! )

 

2-4. 오버레이 파일시스템 (Overlay-Filesystem)  ** 도전과제2

 

[ 기존 패키징의 중복 문제 ]

 - 기존 base-Image 에 필요한 app. 을 쌓아서 올리다 보니 기존 package 중복용량 만큼 저장공간을 더 차지하면서 비용이슈 발생함

 

> UFS (Union FileSystem) 구조 - Ref. Blog

  • 여러 개의 파일 시스템을 하나의 파일 시스템에 마운트 하는 기능
  • 중복되는 파일시스템은 덮어쓴다 (overlay)
  • UFS에서는 기존 레이어(하위 레이어) 위에 새로운 레이어(상위 레이어)가 쌓일 경우, 하위 레이어는 읽기 전용 상태가 된다. 또한, 상위 레이어에서 하위 레이어에 쓰기 작업을 수행할 경우, 하위 레이어를 복사하여 사용(CoW)하기 때문에 상위 레이어에서는 하위 레이어에 아무런 영향을 주지 않는다.

[ 하위 레이어 저장데이터는 수정불가!! 상위 레이어는 overwite 데이터 view 제공 ]

 

[ 저장소에 저장된 이미지 별 Lower-layer 부터 stacking 하여 Latest-layer 에서 Merged View로 보이게 된다. ]

 

[ 실습 따라하기 ** 주의 : 명령어 위치는 OS 버전에 따라 다를 수 있음!! ]

 - overlay filesystem 구성을 통해 merged-view를 이해할 수 있다!!

 

[ More ... ]

더보기

1) 명령어 복사 ( which, rm )

which which
ldd /usr/bin/which
mkdir -p tools/usr/bin;
cp /usr/bin/which tools/usr/bin/;
#======================================#
Lower Dir 2 준비 : rm 복사
/tmp#
/tmp#
/tmp#
/tmp#
/tmp#
/tmp#
which rm;
ldd /bin/rm;
mkdir -p tools/{bin,lib64,lib/ \
x86_64-linux-gnu};
cp /bin/rm tools/bin/;
cp /lib/x86_64-linux-gnu/libc.so.6 \
tools/lib/x86_64-linux-gnu/;
cp /lib64/ld-linux-x86-64.so.2 \
tools/lib64;
sh
ls
ps Lower Dir 1 myroot
tromols which Lower Dir 2

 

2) 오버레이 마운트 준비

  - rootfs 위에 container, work, merge 디렉토리를 overlay 마운트 한다.

## 1. 'rootfs' 디렉토리 만들기
mkdir -p rootfs/{container,work,merge}

## 2. overlay 마운트 하기
mount -t overlay overlay -o \
lowerdir=tools:myroot,\
upperdir=rootfs/container,\
workdir=rootfs/work rootfs/merge

 

 

☞ 위에서 overlay mount 한 결과, 하기와 같이 tmp 이하 동일 depth. 의 디렉토리들이

     마치 rootfs 이하 sub. Directory 처럼 보이는 구조를 볼 수 있다.

 

 

 

 ☞ myroot 원본 파일을 보장해 주면서, UFS 로 마운트 된 구조 상 하위 디렉토리 내 파일 삭제 된 내용을
     상위 디렉토리인 container 에 반영해 준다!!  

 


 [ 실습정리 ]

 # umount /tmp/rootfs/merge

 

> 위의 실습을 통해서 하기와 같이 호스트 root filesystem에 의존하지 않는 컨테이너 전용 root filesystem을 갖을 수 있음을 알 수 있다.

 

> docker container에서는 오버레이 파일시스템을 사용하며, 이미지 레이어 단위로 관리함으로 이미지 중복문제를 해결할 수 있다.

  


3. 컨테이너 격리와 자원 

 > 하기 내용을 따라 하면서,  namespace의 '격리' 라는 개념을 이해할 수 있다.

 

3-1. Namespace 사용방법

  방법 1)  proc 디렉토리 이하 종류 별 네임스페이스 조회 & read-link 로 symbolic link 를 읽어 확인하는 방법

   # ls -al /proc/$$/ns

   # readlink /proc/$$/ns/mnt

 

  방법2) lsns (list namesapce) command 사용 ( -t : 네임스페이스 타입 / -p: 조회할 process id )

  # lsns -p 1

  # lsns -t mnt -p 1

 

3-2. 마운트 네임스페이스 격리

 - # unshare 명령어를 통해 namespace 격리를 할 수 있다. 

[ unshare 명령어에 의해 격리된 mnt 네임스페이스만 다른 PID 를 가지고 있다 ]

 

3-2-1. UTS 네임스페이스의 격리

 > 어떤 곳에 사용할 수 있을까요?

 - Unix Time sharing(서버 나눠쓰기)

 - 호스트, 도메인명 격리

 

3-2-2. PID namespace의 격리

 

[ OS와 컨테이너에서의 PID 1 비교]

 . OS에서의 PID1 은 ...

  - init 프로세스 (커널이 생성) 

  - 시그널 처리

  - 좀비, 고아 프로세스의 처리

  - 죽으면 시스템 패닉(reboot)

 

. 컨테이너에서의 PID1 은 ...

  - unshare 할 때 fork 하여 자식 PID 네임스페이스의 pid1 로 실행

  - 시그널 처리

  - 좀비, 고아 프로세스의 처리

  - 죽으면 컨테이너 종료

 

[ 따라하기 - PID Namespace 만들기 ]

더보기

1) fork process 분리 및 proc 마운트

 - /proc 는 ..

  . 메모리 기반의 가상파일시스템

  . 커널이 관리하는 시스템 기본정보 제공

  . 시스템 모니터링과 분석에 활용

unshare -fp --mount-proc /bin/sh
[ 호스트와 격리된 proc namespace 비교 ]

 

[ 호스트에서 격리된 proc namespace 의 PID1 확인 및 SIGKILL 을 통해 통제할 수 있다 ]

 

 

3-2-3. 네트워크 네임스페이스의 격리

 

 - 물리인스턴스를 가상화 하여, 가상 인터페이스를 만들 경우,

   컨테이너의 네트워크 스택을 격리하여 가상의 네트워크 장치를 사용할 수 있다.  

 - 여러 네트워크 네임스페이스에 걸쳐서 있을 수 있고,

 - 다른 네트워크 네임스페이스로 이동할 수 있다.

 - 가상화 인터페이스가 삭제되면, 물리 인터페이스는 기존 네임스페이스로 복원된다.

 

[ 따라하기 - RED/BLUE 컨테이너 간 통신 ]

더보기

1. net namespace 분리구성 및 veth pair 설정

## 1. veth pair 설정
ip link add veth0 type veth peer name veth1

## 2. RED/BLUE network namespace 생성
ip netns add RED;
ip netns add BLUE;

## 3. RED/BLUE network 에 veth0 연결
ip link set veth0 netns RED;
ip link set veth1 netns BLUE;

## 4. RED/BLUE veth# UP
ip netns exec RED ip link set veth0 up;
ip netns exec BLUE ip link set veth1 up;

## 5. RED/BLUE IP setting
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;

 

 2. 네트워크 비교

## nsenter --net=/var/run/netns/{NS명}
## network namespace 생성 확인 
## root@ip-172-16-1-47:~# ls /var/run/netns
## BLUE  RED

## 1. 호스트와 RED/BLUE 비교
nsenter --net=/var/run/netns/RED   ## RED namespace 분리
ip a
ip route
#------------------------------------------#
nsenter --net=/var/run/netns/BLUE
ip a
ip route
#------------------------------------------#
## 호스트에서 수행
ip a
ip route
#------------------------------------------#

## 2. RED/BLUE pair network Ping test
ping 11.11.11.13    ## RED에서 수행
ping 11.11.11.12    ## Blue에서 수행

## 3. 자원정리
ip netns del RED;
ip netns del BLUE;
ls /var/run/netns;  or   ip netns;  ## 아무것도 없으면 OK !!
[ ping test 및 자원정리 ]

 

3-2-4. User naespace 격리

 

 - UID/GID 넘버스페이스 격리를 통해 컨테이너의 루트권한 문제를 해결함

 - 부모-자식 네임스페이스의 중첩구조를 가짐 ( UID/GID Remapping )

 - docker v1.10 + 부터 기능 지원

 - 보안관점에서 큰 개선점이나 default 설정은 user namespace disable

 

[ 실습 이해 ]

 - 일반계정으로 docker container를 띄워보고

 - docker 가 어떤 권한을 가지는지 확인

 

> docker 에 root 권한을 주는 이유는 ..

  - 설치의 용이성을 위해서 인데,

  - 보안에 취약해 진다!!

 

[ More ... ]

더보기

1. 일반 유저로 docker 설치 후, 호스트와 id 비교

## 1. 일반 계정으로 수행하기 위해 docker 권한주기
## sudo usermod -aG docker {계정명}
sudo usermod -aG docker ubuntu   ## (필수) 권한 주고 재접속!!

## 2. ubuntu 유저로 도커설치 후, 호스트와 ID 비교
#------------------------------------------------#
docker run -it ubuntu /bin/sh  ## terminal #1
id
# [결과] uid=0(root) gid=0(root) groups=0(root)

#------------------------------------------------#
id   ## terminal #2 (host)
# [결과] uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),105(lxd),988(docker)
#------------------------------------------------#

## 2. 컨테이너와 호스트이 User namespace 확인
#--------------------------------------------------#
readlink /proc/$$/ns/user   ## container에서 수행
# [결과] user:[4026531837]

#--------------------------------------------------#
readlink /proc/$$/ns/user   ## host에서 수행
# [결과] user:[4026531837]   동일하다?!
#--------------------------------------------------#

 

 

 2. User namespace 격리

## 1. user namespace 격리
#-----------------------------------------------#
unshare -U --map-root-user /bin/sh   ## terminal #1 에서 수행
## [옵션] --map-user=<uid>|<name>   map current user to uid (implies --user)
id
#-----------------------------------------------#
id ## Host terminal #2 에서 수행
ps -ef | grep "/bin/sh"
# [결과] host 에서 보는 /bin/sh 은 일반(Ubuntu) 유저로 보인다!! 
#-----------------------------------------------#
## 2. readlink 명령어로 체크
readlinkk /proc/$$/ns/user   ## terminal#1,#2 에서 수행 후 비교!!
# [결과] user:[##############] <- 번호가 다름을 알수 있다!!
#-----------------------------------------------#

 

[ 권한 이슈 해결 : sudo sysctl kernel.apparmor_restrict_unprivileged_userns=0 ]

 

 

 

3-3. Cgroups (Control Groups)

 

 - 컨테이너 별로 자원을 분배하고 limit 내에서 운용할 수 있도록 관리함

 - 하나 또는 복수 개의 장치를 묶어서 그룹을 생성

 - 프로세스가 사용하는 리소스를 통제함

 - 자원 할당과 제어를 파일시스템으로 제공함

tree -L 1 /sys/fs/cgroup/cpu  
## ubuntu 24.04 에서는 cgroup 이하에 파일 존재함

 

 

다음 실습을 통해 cgroup의 제한을 통해 컨테이너의 격리된 자원관리에 대해 이해 할 수 있다.

 

[ 실습 - 따라하기 : cgroup 통한 자원제한 ]

더보기

1. 실습준비

  - root 계정으로 진행하며, 하기 tool 설치 필요함

sudo -Es
apt install -y cgroup-tools
apt install -y stress

 

 2. stress 통한 부하테스트 && cpu 자원 제한

## 1. cpu 스트레스 발생 및 모니터링
stress -c 2  ## 터미널 1 에서 수행 ( CPU 2장짜리 모델에서 수행 )
#-------------------------------------------------------------#

top  # 터미널 2 에서 수행, CPU 증가상황 모니터링~  up-to 100%
## 2. mycgroup 생성 및 cpu 자원 제한
cgcreate -a root -g cpu:mycgroup
tree /sys/fs/cgroup/cpu/mycgroup

 

 

 3. cgroup 으로 자원제한 및 모니터링

  - cpu.max  20% 제약 후, 확인

## 1. mycgroup 에서 cpu.max 값 수정 ( default : 100000, 수정 값 : 20000 -> 20% 제약 )
cgset -r cpu.max=20000 mycgroup;
cat /sys/fs/cgroup/mycgroup/cpu.max
#---------------------------------------#
# root@ip-172-16-1-47:/tmp# cat /sys/fs/cgroup/mycgroup/cpu.max
# max 100000
#---------------------------------------#

## 2. cpu.max 조정
cgset -r cpu.max=20000 mycgroup;
cat /sys/fs/cgroup/mycgroup/cpu.max
#---------------------------------------#
# root@ip-172-16-1-47:/tmp# cat /sys/fs/cgroup/mycgroup/cpu.max
# 20000 100000
#---------------------------------------#

 


 [ 수행 결과 ]

> (1) mycgroup : cpu.max 20% 제한 >> (2) mycgroup에서 cpu stress 수행 >> (3) top에서 cpu 2장에 20% 이하 부하가 걸림

 

 

[ 실습환경 정리 ]

- 실습 완료 후, 반드시 자원을 정리하여 불필요한 비용이 발생하지 않도록 주의해 주세요~ !!@...

# CloudFormation 스택 삭제
aws cloudformation delete-stack --stack-name mylab

# [모니터링] CloudFormation 스택 상태 : 삭제 확인
while true; do 
  date
  AWS_PAGER="" aws cloudformation list-stacks \
    --stack-status-filter CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED DELETE_IN_PROGRESS DELETE_FAILED \
    --query "StackSummaries[*].{StackName:StackName, StackStatus:StackStatus}" \
    --output table
  sleep 1
done

 

 


[ 마무리 ]

 - KANS gasidas님의 과정학습과 kakao tech 김삼영님의 컨텐츠 "도커 없이 컨테이너 만들기" 과정을 따라 하다보니 도커와 자원격리에

   대한 이해를 할 수 있게 되어 좋았습니다. 가상화 기술에 대한 히스토리가 그려지는 것 같아 다음 학습이 기대가 됩니다.

 > chroot 로 시작해서, namespace 별 가상화 자원의 격리 기술을 바탕으로 docker 가 탄생하게 되고, 이에 대한 효율적 관리를 위해 google 을 통해 kubernetes 기술이 CNCF에 제공되면서 현재, 우리가 알고 사용하고 있는 k8s/docker 생태계가 구축되었다.


 

 

[ 도움이 되는 참고링크 모음 ]

더보기