이번에도 어김없이 가시다님이 운영하시는 스터디에 참여하게 되었습니다.
'Kubernetes Advanced Networking Study' 입니다.
그럼 첫 주차 공유 시작하겠습니다.
도커란?
도커(Docker)는 가상실행 환경을 제공해주는 오픈소스 플랫폼입니다. 도커에서는 이 가상실행 환경을 '컨테이너(Container)'라고 부릅니다.
- 좀 더 정확히 표현하는 용어는 '컨테이너화된 프로세스(Containerized Process)' 이다
- 도커 플랫폼이 설치된 곳이라면 컨테이너로 묶인 애플리케이션을 어디서든 실행할 수 있는 장점을 가집니다.
아키텍처
도커 설치 및 확인
# [터미널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
컨테이너 실행 및 확인
nginx 컨테이너를 백그라운드로 실행 - 링크
# nginx 이미지를 컨테이너 백그라운드로 실행
# -d 는 Detached 모드로 컨테이너를 실행. 컨테이너를 백그라운드에서 동작하는 애플리케이션으로써 실행하도록 설정.
# Detached 모드인 컨테이너는 반드시 컨테이너에서 프로그램이 실행돼야 하며 프로그램이 실행되지 않으면 컨테이너는 종료됩니다.
docker run -d nginx
docker ps
# 실행중인 컨테이너의 ID만 확인
docker ps -q
# 컨테이너 상세 정보 확인
# docker inspect '<NAME> 혹은 <ID>'
docker inspect $(docker ps -q)
# 컨테이너(=Instance)의 IP 정보 확인(JSON) - 링크
# docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q)
ping 172.17.0.2
# 호스트 네트워크 인터페이스 정보 확인
ip -c a
brctl show
# curl 로 http 접속 테스트 - 링크 링크2
curl `docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $(docker ps -q)` | grep -o '<title>.*</title>'
curl -s 172.17.0.2 | grep -o '<title>.*</title>'
Manage Docker as a non-root user & Socket 소켓 - Link
# [터미널2] 일반 유저 ubuntu 로 실습 진행
whoami
# 도커 서비스 상태 확인
sudo systemctl status docker -l --no-pager
...
TriggeredBy: ● docker.socket
...
Aug 24 23:56:53 MyServer dockerd[5178]: time="2024-08-24T23:56:53.076645163+09:00" level=warning msg="failed to close stdin: NotFound: task 0a3d8b3ef5640679c072fdf1b4a08fd53fcdeb679f7e720ff4131330e07cc4d1 not found: not found"
# 도커 서버 정보 획득 실패
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
정보 획득 실패
Unix Domain Socket by ChatGPT : 예) UDS 사용 - mysql 로컬 접속 시 , Istio proxy 와 Envoy 프로세스(XDS, SDS)간 로컬 통신 시
- Unix Domain은 동일한 시스템 내에서 실행되는 프로세스들 간의 통신을 의미하며, "Domain"은 로컬 시스템 내의 통신 영역을 나타냅니다.
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 재접속 후 확인
#
sudo systemctl status docker -l --no-pager
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
일반 유저로 실행
[심화] 컨테이너가 host의 docker socker file 공유로 도커 실행 - Link1 , Link2
#
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
--------------------
Jenkins 컨테이너 : 도커 빌드 과정에서 활용 - DockerHub , Github , Docs
# 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
도커 설치 후 기본 정보 확인
# [터미널1] 관리자 권한
# 프로세스 확인 - 셸변수
ps -ef
pstree -p
# 시스템에 (마운트 된) disk free 디스크 여유 공간 확인
df -hT
# 네트워크 정보 확인 >> docker0 네트워크 인터페이스가 추가됨, 현재는 DOWN 상태
ip -br -c addr
ip -c addr
ip -c link
ip -br -c link
ip -c route
# 이더넷 브리지 정보 확인
brctl show
# iptables 정책 확인
iptables -t filter -S
iptables -t nat -S
## filter 에 FORWARD 가 기존 ACCEPT 에서 DROP 로 변경됨
## filter 에 FORWARD 에 docker0 에서 docker0 혹은 외부로 전달 허용 정책이 추가됨
iptables -t filter -S
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
-N DOCKER
-N DOCKER-ISOLATION-STAGE-1
-N DOCKER-ISOLATION-STAGE-2
-N DOCKER-USER
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
## nat POSTROUTING 에 172.17.0.0/16 에서 외부로 전달 시 매스커레이딩(SNAT) 정책이 추가됨
iptables -t nat -S
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-N DOCKER
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
삭제
# 실행 및 종료된 컨테이너까지 전부삭제
docker rm -f $(docker ps -a -q)
컨테이너 격리
이게 돼요? 도커 없이 컨테이너 만들기 1 : Chroot + 탈옥 - Youtube , Github , Docs
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/
#
chroot myroot /bin/sh
--------------------
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 /
ls명령 실행 확인
chroot 에서 ps 실행해보기 - Link
# 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
umount /tmp/myroot/proc
mount -t proc
ps실행해보기
남이 만든 이미지 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
확인
탈옥 코드 : vi escape_chroot.c
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
mkdir(".out", 0755);
chroot(".out");
chdir("../../../../../");
chroot(".");
return execl("/bin/sh", "-i", NULL);
}
탈옥 코드를 컴파일하고 new-root 에 복사
# 컴파일
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 /
탈옥 성공!
이게 돼요? 도커 없이 컨테이너 만들기 2 : 마운트 네임스페이스 + Pivot_root
마운트 네임스페이스
주요 명령어
pivot_root
# pivot_root [new-root] [old-root]
## 사용법은 심플합니다 ~ new-root와 old-root 경로를 주면 됩니다
mount
# mount -t [filesystem type] [device_name] [directory - mount point]
## root filesystem tree에 다른 파일시스템을 붙이는 명령
## -t : filesystem type ex) -t tmpfs (temporary filesystem : 임시로 메모리에 생성됨)
## -o : 옵션 ex) -o size=1m (용량 지정 등 …)
## 참고) * /proc/filesystems 에서 지원하는 filesystem type 조회 가능
unshare
# unshare [options] [program] [arguments]]
## "새로운 네임스페이스를 만들고 나서 프로그램을 실행" 하는 명령어입니다
# [터미널1]
unshare --mount /bin/sh
-----------------------
# 아래 터미널2 호스트 df -h 비교 : mount unshare 시 부모 프로세스의 마운트 정보를 복사해서 자식 네임스페이스를 생성하여 처음은 동일
df -h
-----------------------
# [터미널2]
df -h
# [터미널1]
-----------------------
#
mkdir new_root
mount -t tmpfs none new_root
ls -l
tree new_root
## 마운트 정보 비교 : 마운트 네임스페이스를 unshare
df -h
mount | grep new_root
findmnt -A
## 파일 복사 후 터미널2 호스트와 비교
cp -r myroot/* new_root/
tree new_root/
-----------------------
# [터미널2]
cd /tmp
ls -l
tree new_root
df -h
mount | grep new_root
findmnt -A
## 안보이는 이유 : 마운트 네임스페이스를 unshare 된 상태
tree new_root/
pivot_root
# 터미널1
-----------------------
mkdir new_root/put_old
## pivot_root 실행
cd new_root # pivot_root 는 실행 시, 변경될 root 파일시스템 경로로 진입
pivot_root . put_old # [신규 루트] [기존 루트]
##
cd /
ls / # 터미널2와 비교
ls put_old
-----------------------
# 터미널2
ls /
탈옥 시도
# 터미널1
-----------------------
./escape_chroot
ls /
exit
exit
-----------------------
탈옥 실패 ..
이게 돼요? 도커 없이 컨테이너 만들기 3 : 격리 Namespece - Docs , Youtube
네임스페이스와 관련된 프로세스의 특징
1. 모든 프로세스들은 네임스페이스 타입별로 특정 네임스페이스에 속합니다.
2. Child는 Parent의 네임스페이스를 상속 받습니다.
3. 프로세스는 네임 스페이스 타입별로 일부는 호스트 네임스페이스를 사용하고 일부는 컨데이너의 네임스페이스를 사용할 수 있습니다.
실습 : 터미널 2개 모두 관리자 권한으로 진행
프로세스 별 네임스페이스 확인
# [터미널 1,2] 관리자
sudo su -
cd /tmp
# 네임스페이스 확인 방법 1 : 프로세스 별 네임스페이스 확인
ls -al /proc/$$/ns
lrwxrwxrwx 1 root root 0 Aug 25 13:45 cgroup -> 'cgroup:[4026531835]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 ipc -> 'ipc:[4026531839]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 mnt -> 'mnt:[4026531841]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 net -> 'net:[4026531840]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 pid -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 pid_for_children -> 'pid:[4026531836]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 time -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 time_for_children -> 'time:[4026531834]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 user -> 'user:[4026531837]'
lrwxrwxrwx 1 root root 0 Aug 25 13:45 uts -> 'uts:[4026531838]'
## 특정 네임스페이스의 inode 값만 확인
readlink /proc/$$/ns/mnt
readlink /proc/$$/ns/net
# 네임스페이스 확인 방법 2 : lsns - List system namespaces
lsns -h
lsns -p 1
lsns -p $$
## -t 네임스페이스 타입 , -p 조회할 PID
## NPROCS : 해당 네임스페이스에 속해있는 프로세스 갯수
## PID : 해당 네임스페이스의 (최초) 주인 프로세스
lsns -t mnt -p 1
lsns -t mnt -p $$
1. 마운트 네임스페이스 MOUTNT(mnt) Namespace : 2002년, 마운트 포인트 격리, 최초의 네임스페이스
- echo $$ - 링크
# PID 1과 현재 Shell 속한 프로세스의 MNT NS 정보 확인
lsns -t mnt -p 1
lsns -t mnt -p $$
# [터미널1] /tmp 디렉터리
# unshare -m [명령어] : -m 옵션을 주면 [명령어]를 mount namespace 를 isolation 하여 실행합니다
unshare -m # *[명령어]를 지정하지 않으면 환경변수 $SHELL 실행
-----------------------------------
# NPROCS 값과 PID 값의 의미 확인
lsns -p $$
NS TYPE NPROCS PID USER COMMAND
4026531834 time 112 1 root /sbin/init
4026531835 cgroup 112 1 root /sbin/init
4026531836 pid 112 1 root /sbin/init
4026531837 user 112 1 root /sbin/init
4026531838 uts 108 1 root /sbin/init
4026531839 ipc 112 1 root /sbin/init
4026531840 net 112 1 root /sbin/init
4026532206 mnt 2 5834 root -bash
# PID 1과 비교
lsns -p 1
# 빠져나오기
exit
-----------------------------------
2. UTS 네임스페이스 Namespace : 2006년, Unix Time Sharing (여러 사용자 작업 환경 제공하고자 서버 시분할 나눠쓰기), 호스트명, 도메인명 격리
# unshare -u [명령어]
# -u 옵션을 주면 [명령어]를 UTS namespace 를 isolation 하여 실행
# [터미널1] /tmp 디렉터리
unshare -u
-----------------------------------
lsns -p $$
lsns -p 1
## 기본은 부모 네임스페스의 호스트 네임을 상속
hostname
## 호스트 네임 변경
hostname KANS
## 아래 터미널2에서 hostname 비교
hostname
exit
-----------------------------------
# [터미널2] /tmp 디렉터리
hostname
격리 확인
3. IPC 네임스페이스 : 2006년, Inter-Process Communication 격리, 프로세스 간 통신 자원 분리 관리 - Shared Memory, Pipe, Message Queue 등
# [터미널1] /tmp 디렉터리
unshare -i
-----------------------------------
lsns -p $$
lsns -p 1
exit
-----------------------------------
[추가 실습] 2개의 컨테이너 간 IPC 네임스페이스 공유
# [터미널1]
ipcs -m
docker run --rm --name test1 --ipc=shareable -it ubuntu bash
---------------------
ipcs -m
ipcmk -M 2000
Shared memory id: 0
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x6427dde0 0 root 644 2000 0
lsns -p $$
# 아래 실습 확인 후 종료
exit
---------------------
# [터미널2]
# 호스트에서 확인
ipcs -m
# 컨테이너 생성 시 IPC 공유 : 해당 컨테이너는 test1 컨테이너와 IPC 네임스페이스를 공유
docker run --rm --name test2 --ipc=container:test1 -it ubuntu bash
---------------------
ipcs -m
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x6427dde0 0 root 644 2000 0
lsns -p $$
# 실습 확인 후 종료
exit
---------------------
컨테이너 공유 후 동일한 ipc사용 확인
4. PID 네임스페이스 : 2008년, Process ID 격리
ps -ef | head -n 3
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 09:57 ? 00:00:04 /sbin/init
root 2 0 0 09:57 ? 00:00:00 [kthreadd]
- unshare 할 때 fork 하여, 자식 PID 네임스페이스의 pid 1로 실행
- pid 1 (init) 이 종료되면 pid namespace 도 종료
# unshare -p [명령어]
## -p 옵션을 주면 [명령어]를 PID namespace 를 isolation 하여 실행합니다
## -f(fork) : PID namespace 는 child 를 fork 하여 새로운 네임스페이스로 격리함
## --mount-proc : namespace 안에서 ps 명령어를 사용하려면 /proc 를 mount 하기위함
# [터미널1] /proc 파일시스템 마운트
echo $$
unshare -fp --mount-proc /bin/sh
--------------------------------
# 터미널2 호스트와 비교
echo $$
ps -ef
ps aux
# 내부에서 PID NS 확인 : 아래 터미널2에서 lsns -t pid -p <위 출력된 PID>와 비교
lsns -t pid -p 1
--------------------------------
# [터미널2]
ps -ef
ps aux
ps aux | grep '/bin/sh'
root 6186 0.0 0.0 6192 1792 pts/2 S 15:08 0:00 unshare -fp --mount-proc /bin/sh
root 6187 0.0 0.0 2892 1664 pts/2 S+ 15:08 0:00 /bin/sh
# 터미널1 PID NS와 비교
lsns -t pid -p <위 출력된 PID>
lsns -t pid -p 6187
확인
- 호스트에서 컨테이너 프로세스 종료 해보기
# [터미널1]
--------------------------------
# fork
sleep 10000
# 아래 종료로 자동으로 sleep 가 exit 됨
echo $$
# 아래 종료로 자동으로 exit됨 : 컨테이너의 PID 1 프로세스 종료 시
--------------------------------
echo $$
# [터미널2]
ps aux | grep sleep
## 호스트에서 sleep 종료 시켜보기 : 어떻게 되는가?
kill -l
kill -SIGKILL $(pgrep sleep)
## 호스트에서 /bin/sh 종료 시켜보기 : 어떻게 되는가?
ps aux | grep '/bin/sh'
kill -SIGKILL <위 출력된 PID>
kill -9 6187
- 도커 종료 Exit status - Docs KrBlog
# Exit code 125 indicates that the error is with Docker daemon itself.
docker run --foo busybox; echo $?
flag provided but not defined: --foo
See 'docker run --help'.
125
# Exit code 126 indicates that the specified contained command can't be invoked.
# The container command in the following example is: /etc; echo $?.
docker run busybox /etc; echo $?
docker: Error response from daemon: Container command '/etc' could not be invoked.
126
# Exit code 127 indicates that the contained command can't be found.
docker run busybox foo; echo $?
docker: Error response from daemon: Container command 'foo' not found or does not exist.
127
# Other exit codes : Any exit code other than 125, 126, and 127 represent the exit code of the provided container command.
docker run busybox /bin/sh -c 'exit 3'
echo $?
3
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f02d387b3c00 busybox "/bin/sh -c 'exit 3'" 8 seconds ago Exited (3) 6 seconds ago youthful_hypatia
...
# 컨테이너 삭제
docker rm -f $(docker ps -aq)
USER 네임스페이스
2012년, UID/GID 넘버스페이스 격리(Remap_, 컨테이너의 루트권한 문제를 해결함, 부모-자식 네임스페이스의 중첩 구조
-네임스페이스 안과 밖의 UID/GID 를 다르게 설정할 수 있음
-호스트 상에서는 권한이 없는 일반 유저를 컨테이너 안에서는 모든 권한을 가지게 할 수 있음
사전 준비 : 터미널1(ubuntu 일반 유저, docker 실행 가능 상태) , 터미널2(ubuntu 일반 유저)
# 터미널1,2
exit
whoami
# 터미널1
docker run -it ubuntu /bin/sh
-----------------------------
# 아래 터미널2와 비교
whoami
id
# 아래 터미널2와 비교
ps -ef
# User 네임스페이스는 도커 컨테이너 실행 시, 호스트 User 를 그대로 사용
readlink /proc/$$/ns/user
lsns -p $$
NS TYPE NPROCS PID USER COMMAND
4026531834 time 2 1 root /bin/sh
4026531837 user 2 1 root /bin/sh
4026532208 mnt 2 1 root /bin/sh
4026532209 uts 2 1 root /bin/sh
4026532210 ipc 2 1 root /bin/sh
4026532211 pid 2 1 root /bin/sh
4026532212 net 2 1 root /bin/sh
4026532273 cgroup 2 1 root /bin/sh
# 아래 동작 확인 후 종료
exit
-----------------------------
# 터미널2
whoami
id
## root 로 실행됨
ps -ef |grep "/bin/sh"
ubuntu 6733 5348 0 15:34 pts/0 00:00:00 docker run -it ubuntu /bin/sh
root 6790 6768 0 15:34 pts/0 00:00:00 /bin/sh
##
readlink /proc/$$/ns/user
lsns -p $$
lsns -p $$ -t user
NS TYPE NPROCS PID USER COMMAND
4026531837 user 5 2391 ubuntu /lib/systemd/systemd --user
- 컨테이너를 탈취 후, 해당 프로세스를 기반으로 호스트에 Action 이 가능할 경우, root 계정 권한 실행이 가능 ⇒ 보안상 취약
# 터미널1
unshare -U --map-root-user /bin/sh
-----------------------------
# 내부에서는 여전히 root로 보임
whoami
id
# User 네임스페이스를 호스터(터미널2)와 비교
readlink /proc/$$/ns/user
lsns -p $$
# 아래 동작 확인 후 종료
exit
-----------------------------
# 터미널2
readlink /proc/$$/ns/user
lsns -p $$
## ubuntu 로 실행됨
ps -ef |grep "/bin/sh"
ubuntu 6874 5348 0 15:42 pts/0 00:00:00 /bin/sh
#
내부에서는 루트로 보이지만 host에서는 ubuntu계정으로 보인다.
이게 돼요? 도커 없이 컨테이너 만들기 4 : 자원 관리, Cgroups - Docs , Youtube
#
mount -t cgroup
mount -t cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
#
findmnt -t cgroup2
TARGET SOURCE FSTYPE OPTIONS
/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
# cgroup2 이외에 proc, bpf 도 있음
findmnt -A
TARGET SOURCE FSTYPE OPTIONS
/ /dev/nvme0n1p1 ext4 rw,relatime,discard,errors=remount-ro
...
├─/proc proc proc rw,nosuid,nodev,noexec,relatime
...
├─/sys sysfs sysfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/kernel/security securityfs securityfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/cgroup cgroup2 cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
│ ├─/sys/fs/pstore pstore pstore rw,nosuid,nodev,noexec,relatime
│ ├─/sys/firmware/efi/efivars efivarfs efivarfs rw,nosuid,nodev,noexec,relatime
│ ├─/sys/fs/bpf bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700
...
# cgroupv1 만 지원 시, cgroup2 출력되지 않음
grep cgroup /proc/filesystems
nodev cgroup
nodev cgroup2
stat -fc %T /sys/fs/cgroup/
cgroup2fs
# 터미널2
sleep 100000
# /proc에 cgroup 정보 확인
cat /proc/cgroups
cat /proc/$(pgrep sleep)/cgroup
0::/user.slice/user-1000.slice/session-7.scope
tree /proc/$(pgrep sleep) -L 2
...
├── ns
│ ├── cgroup -> cgroup:[4026531835]
│ ├── ipc -> ipc:[4026531839]
│ ├── mnt -> mnt:[4026531841]
│ ├── net -> net:[4026531840]
...
# cgroup 목록 확인
ls /sys/fs/cgroup
cat /sys/fs/cgroup/cgroup.controllers
#
tree /sys/fs/cgroup/ -L 1
tree /sys/fs/cgroup/ -L 2
tree /sys/fs/cgroup/user.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 1
tree /sys/fs/cgroup/user.slice/user-1000.slice -L 2
# 터미널1,2 관리자로 실습 진행
sudo su -
whoami
# 툴 설치
apt install cgroup-tools stress
# 터미널2 : 아래 stress 실행 후 CPU 사용률 확인
htop
# 터미널1에서 실습 진행
# 먼저 부하 발생 확인
stress -c 1
# 디렉터리 이동
cd /sys/fs/cgroup
# 서브 디렉터리 생성 후 확인 확인
mkdir test_cgroup_parent && cd test_cgroup_parent
tree
# 제어 가능 항목 확인
cat cgroup.controllers
# cpu를 subtree이 추가하여 컨트롤 할 수 있도록 설정 : +/-(추가/삭제)
cat cgroup.subtree_control
echo "+cpu" >> /sys/fs/cgroup/test_cgroup_parent/cgroup.subtree_control
# cpu.max 제한 설정 : 첫 번쨰 값은 허용된 시간(마이크로초) 두 번째 값은 총 기간 길이 - 1/10 실행 설정
echo 100000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max
# test용 자식 디렉토리를 생성하고, pid를 추가하여 제한을 걸어
mkdir test_cgroup_child && cd test_cgroup_child
echo $$ > /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child/cgroup.procs
cat /proc/$$/cgroup
# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1
# 값 수정
echo 1000000 1000000 > /sys/fs/cgroup/test_cgroup_parent/cpu.max
# 부하 발생 확인 : 터미널2에 htop 확인
stress -c 1
# cgroup 삭제
exit
sudo su -
rmdir /sys/fs/cgroup/test_cgroup_parent/test_cgroup_child
rmdir /sys/fs/cgroup/test_cgroup_parent
# 아래는 cgroup v1 경우
---------------------------
## 1. 제어그룹 생성 : mycgroup
### -a : owner 설정 (control group's file)
### -g : cgroup 설정 <controllers>:<path>
### -g cpu:mycgroup ~ cpu controller 를 사용하고 path 는 mycgroup
cgcreate -a root -g cpu:mycgroup
tree /sys/fs/cgroup/cpu/mycgroup
## 2. 제어그룹 리소스 설정 : CPU 사용률 설정
### cpu 사용률(%CPU)
### cpu.cfs_quota_us / cat cpu.cfs_period_us * 100
### 참고 1000us = 1ms
### cpu사용률(30%)을 설정 (30,000/100,000)x100=30%
cgset -r cpu.cfs_quota_us=30000 mycgroup
cat /sys/fs/cgroup/cpu/mycgroup/cpu.cfs_quota_us
## 3. 제어그룹 프로세스 할당 : stress 실행
cgexec -g cpu:mycgroup stress -c 1
제한을 걸고 부하를 주기
1.1 RED ↔ BLUE 네트워크 네임스페이스 간 통신
네트워크 네임스페이스 생성 시 호스트 네트워크와 구별된다
실습 : 터미널1~3 모두 관리자로 실습 진행
# 터미널1~3 관리자
sudo su -
whoami
# veth (가상 이더넷 디바이스) 생성, man ip-link
ip link add veth0 type veth peer name veth1
# veth 생성 확인(상태 DOWN), ifconfig 에는 peer 정보 확인 안됨
# very pair 정보 확인 : ({iface}@if{pair#N})
ip -c link
ip -c addr # 축약 ip -c a
ifconfig -a
# 네트워크 네임스페이스 생성 , man ip-netns
ip netns add RED
ip netns add BLUE
# 네트워크 네임스페이스 확인
ip netns list
# veth0 을 RED 네트워크 네임스페이스로 옮김
ip link set veth0 netns RED
ip netns list
## 호스트의 ip a 목록에서 보이지 않음, veth1의 peer 정보가 변경됨
ip -c link
## RED 네임스페이스에서 ip a 확인됨(상태 DOWN), peer 정보 확인, link-netns RED, man ip-netns
ip netns exec RED ip -c a
# veth1 을 BLUE 네트워크 네임스페이스로 옮김
ip link set veth1 netns BLUE
ip -c link
ip netns exec BLUE ip -c a
# veth0, veth1 상태 활성화(state UP)
ip netns exec RED ip link set veth0 up
ip netns exec RED ip -c a
ip netns exec BLUE ip link set veth1 up
ip netns exec BLUE ip -c a
# veth0, veth1 에 IP 설정
ip netns exec RED ip addr add 11.11.11.2/24 dev veth0
ip netns exec RED ip -c a
ip netns exec BLUE ip addr add 11.11.11.3/24 dev veth1
ip netns exec BLUE ip -c a
# 터미널1 (RED 11.11.11.2)
## nsenter : 네임스페이스에 attach 하여 지정한 프로그램을 실행
tree /var/run/netns
nsenter --net=/var/run/netns/RED
ip -c a
## neighbour/arp tables management , man ip-neighbour
ip -c neigh
## 라우팅 정보, iptables 정보
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널2 (호스트)
lsns -t net # nsenter 실행 후 TYPE(net) CMD(-bash) 생성 확인
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a
ip -c neigh
ip -c route
iptables -t filter -S
iptables -t nat -S
# ping 통신 확인
# 터미널3 (BLUE 11.11.11.3)
tcpdump -i veth1
ip -c neigh
exit
# 터미널1 (RED 11.11.11.2)
ping 11.11.11.3 -c 1
ip -c neigh
exit
# 삭제
ip netns delete RED
ip netns delete BLUE
격리 확인
1.2 RED ← Bridge(br0) → BLUE 간 통신
arp table, route table, iptables 와 호스트의 bridge fdb 를 통하여 통신
기본설정 확인
# 네트워크 네임스페이스 및 veth 생성
ip netns add RED
ip link add reth0 type veth peer name reth1
ip link set reth0 netns RED
ip netns add BLUE
ip link add beth0 type veth peer name beth1
ip link set beth0 netns BLUE
# 확인
ip netns list
ip -c link
ip netns exec RED ip -c a
ip netns exec BLUE ip -c a
# 브리지 정보 확인
brctl show
# br0 브리지 생성
ip link add br0 type bridge
# br0 브리지 정보 확인
brctl show br0
brctl showmacs br0
brctl showstp br0
# reth1 beth1 을 br0 연결
ip link set reth1 master br0
ip link set beth1 master br0
brctl show br0
brctl showmacs br0
ip -br -c link
# reth0 beth0 에 IP 설정 및 활성화, br0 활성화
ip netns exec RED ip addr add 11.11.11.2/24 dev reth0
ip netns exec BLUE ip addr add 11.11.11.3/24 dev beth0
ip netns exec RED ip link set reth0 up; ip link set reth1 up
ip netns exec BLUE ip link set beth0 up; ip link set beth1 up
ip link set br0 up
ip -br -c addr
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
RED
# 터미널2 (호스트)
brctl showmacs br0
bridge fdb show
bridge fdb show dev br0
iptables -t filter -S
iptables -t filter -L -n -v
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip -c a;echo; ip -c route;echo; ip -c neigh
## 현재 네트워크 네임스페이스 정보 확인
ip netns identify $$
BLUE
==============================================================
# 터미널2 (호스트)
# ping 통신 전 사전 설정
## iptables 정보 확인
iptables -t filter -S | grep '\-P'
-P INPUT ACCEPT
-P FORWARD DROP
-P OUTPUT ACCEPT
iptables -nvL -t filter
## Ubuntu 호스트에서 패킷 라우팅 설정 확인 : 커널의 IP Forwarding (routing) 기능 확인 - 0(off), 1(on)
## echo 1 > /proc/sys/net/ipv4/ip_forward
cat /proc/sys/net/ipv4/ip_forward
1
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2) >> ping 왜 실패했을까요?
ping -c 1 11.11.11.3
# 터미널2 (호스트)
tcpdump -l -i br0
watch -d 'iptables -v --numeric --table filter --list FORWARD'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0
포워드 정책에 의해서 drop된다
RED → BLUE ping 허용 설정 - 링크
호스트 입장에서는 "외부(RED, src) → 외부(BLUE, dst)" 패킷이므로 FORWARD 체인의 filter 테이블 룰을 봐야합니다 → 허용 정책 추가하자 - 링크
# 터미널2 (호스트)
# iptables 설정 정보 확인
iptables -t filter -S
iptables -t nat -S | grep '\-P'
# iptables 설정 추가 -t(table), -I(insert chain), -j(jump to - ACCEPT 허용)
iptables -t filter -I DOCKER-USER -j ACCEPT
iptables -nvL -t filter
iptables -t filter -S
iptables -t nat -S | grep '\-P'
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
==============================================================
# ping 통신 테스트
# 터미널1 (RED 11.11.11.2)
ping -c 1 11.11.11.3
ip -c neigh
# 터미널2 (호스트)
watch -d 'iptables -v --numeric --table filter --list FORWARD;echo;iptables -v --numeric --table filter --list DOCKER-USER;echo;iptables -v --numeric --table filter --list DOCKER-ISOLATION-STAGE-1'
tcpdump -l -i br0
## 정보 확인
ip -c neigh
# 터미널3 (BLUE 11.11.11.3)
tcpdump -l -i beth0
ip -c neigh
==============================================================
# (옵션) 추가 방안1 : 출발지 IP 11.2, 11.3 허용
## -t(table), -A(APPEND chain rule), -s(출발지), -j(jump to - ACCEPT 허용)
iptables -t filter -A FORWARD -s 11.11.11.2/32 -j ACCEPT
iptables -t filter -A FORWARD -s 11.11.11.3/32 -j ACCEPT
## 추가 정책 삭제 시
iptables -t filter -D FORWARD -s 11.11.11.2/32 -j ACCEPT
iptables -t filter -D FORWARD -s 11.11.11.3/32 -j ACCEPT
# (옵션) 추가 방안2 : 11.11.11.0/24 대역 출발지 허용
iptables -t filter -A FORWARD -s 11.11.11.0/24 -j ACCEPT
## 추가 정책 삭제 시
iptables -t filter -D FORWARD -s 11.11.11.0/24 -j ACCEPT
# (옵션) 추가 방안3 : FORWARD 기본 정책 허용으로 변경
iptables -t filter -P FORWARD ACCEPT
## 추가 정책 삭제 시
iptables -t filter -P FORWARD DROP
확인
1.3 RED/BLUE → 호스트 & 외부(인터넷) 통신
호스트에 RED/BLUE와 통신 가능한 IP 설정 및 라우팅 추가, iptables NAT 를 통하여 통신
(실습2번 진행한 경우에는 Skip) RED 와 BLUE 설정
ip netns add RED
ip link add reth0 netns RED type veth peer name reth1
ip netns add BLUE
ip link add beth0 netns BLUE type veth peer name beth1
ip link add br0 type bridge
ip link set reth1 master br0
ip link set beth1 master br0
ip netns exec RED ip addr add 11.11.11.2/24 dev reth0
ip netns exec BLUE ip addr add 11.11.11.3/24 dev beth0
ip netns exec RED ip link set reth0 up; ip link set reth1 up;
ip netns exec BLUE ip link set beth0 up; ip link set beth1 up;
ip link set br0 up
# iptables -t filter -A FORWARD -s 11.11.11.0/24 -j ACCEPT
iptables -t filter -I DOCKER-USER -j ACCEPT
호스트에서 RED 나 BLUE 로 ping 통신 → RED 에서 외부로 통신
# 터미널1 (RED 11.11.11.2)
nsenter --net=/var/run/netns/RED
tcpdump -i any
# 터미널3 (호스트)
exit
tcpdump -i br0 -n
# 터미널2 (호스트) >> 호스트에서 RED 로 통신이 안되는 이유가 무엇일까요?
ping -c 1 11.11.11.2
ip -c route
ip -c addr
==============================================================
# 터미널2 (호스트) >> br0 에 IP 추가(라우팅 정보)
ip addr add 11.11.11.1/24 dev br0
ping -c 1 11.11.11.2
ping -c 1 11.11.11.3
# 터미널1 (RED 11.11.11.2) >> 192.168.50.10 와 통신이 안되는 이유는 무엇일까요?
ping -c 1 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ip -c addr
==============================================================
# 터미널3 (호스트)
tcpdump -i any icmp -n
# 터미널1 (RED 11.11.11.2)
ip route add default via 11.11.11.1
ping -c 1 192.168.50.10
ip -c route
ping -c 1 8.8.8.8 >> 외부 대역(인터넷)과 통신이 안되는 이유가 무엇일까요?
# 터미널2 (호스트)
iptables -S -t nat
iptables -nvL -t nat
## POSTROUTING : 라우팅 Outbound or 포워딩 트래픽에 의해 트리거되는 netfilter hook
## POSTROUTING 에서는 SNAT(Source NAT) 설정
iptables -t nat -A POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
watch -d 'iptables -v --numeric --table nat --list POSTROUTING'
iptables -nvL -t nat
conntrack -L --src-nat
# 터미널1 (RED 11.11.11.2)
ping -c 1 8.8.8.8
exit
# 터미널3 (BLUE 11.11.11.3)
nsenter --net=/var/run/netns/BLUE
ip route add default via 11.11.11.1
ping -c 1 8.8.8.8
exit
# 삭제
ip netns delete RED
ip netns delete BLUE
ip link delete br0
iptables -t filter -D DOCKER-USER -j ACCEPT
iptables -t nat -D POSTROUTING -s 11.11.11.0/24 -j MASQUERADE
확인
'study > KANS 3기' 카테고리의 다른 글
KANS 3기 3주차 첫번째 (0) | 2024.09.18 |
---|---|
KANS 3기 3주차 실습환경 구축 (0) | 2024.09.08 |
KANS 3기 2주차 두번째 (0) | 2024.09.07 |
KANS 3기 2주차 첫번째 (0) | 2024.09.07 |
KANS 3기 1주차 두번째 (0) | 2024.08.31 |