레이블이 tmpfs인 게시물을 표시합니다. 모든 게시물 표시
레이블이 tmpfs인 게시물을 표시합니다. 모든 게시물 표시

linux disk or memory /tmp directory

# go 서버(k8s pod)에 multipart 형식으로 파일을 업로드하면 context.multipartfrom() 에서 
# /tmp 위치에 multipart-3000453554 등의 이름으로 임시파일을 생성하게 된다.
# 여러 linux 배포판에서 disk IO 를 줄이기 위해 /tmp 는 디폴트로 tmpfs(RAM based) 파일시스템을 사용한다고 한다.
# 사용한 리눅스 배포판은 Debian GNU/Linux 9 (stretch) 이다.
# 그런데 df, mount, findmnt 명령에는 /tmp 마운트(파일시스템) 정보가 보이지 않는다.

# 파일 업로드를 진행하면
# /tmp/multipart... 파일이 점점 커지면서
watch -n 1 ls -ahl /tmp

# free 는 점점 줄어 들고 있는것 처럼 보인다.
# -s: repeat printing every N seconds
# -w: wide output
# -m: show output in mebibytes
free -s 1 -w -m

# /tmp 사용량을 보면 mounted on / 정보가 나온다.
# 이는 disk 를 사용하고 있다는 얘기다.
# 메모리 뿐만 아니라  / 의 disk 용량도 줄어들고 있는것이 확인된다.
watch -n 1 df -m /tmp

# 다시 free 결과를 보면 cache(pages with actual contents of files or block devices) 용량이 증가한다.
# 예전 free 관련에서 정리한 포스팅을 다시 참고해보면
# cache 의 목적은 최근 자주 사용되는 데이터를 메모리에 보관하여 재사용시 디스크까지 가서 않고 메모리에서 빠르게 액세스/로딩을 위해 사용된다.
# cache 사용량이 증가했지만 
# 가용메모리 = free + buffers + cached 로 사용할 수 있는 메모리가 줄어든것이 아니였다.
# 실제로 kubectl top 로 보면 메모리 사용량은 증가하지 않았다.
# kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl top pod -n my-namespace

# 결론... /tmp 는 디스크를 사용하고 있었다.

linux namespace 사용하기

# 2002년 리눅스 커널에 포함된 namespace(mnt, pid, net, ipc, cgroup...타입) 로 isolation 구현
# 모든 프로세스들은 특정 네임스페이스에 속하고 unshare 로 새로운 네임스페이스에서 프로세스를 실행할 수 있다. 네임스페이스는 exit 로 빠져나올 수 있다.
# unshare 시 프로세스를 명시하지 않으면 ${SHELL}값(/bin/sh 또는 /bin/bash등)이 디폴트로 실행된다.


#####


# mnt namespace
# 부모(호스트)로 부터 격리된 mount타입(-m, --mount)의 네임스페이스로 /bin/bash 프로그램 실행� 
unshare --mount /bin/sh

# mount_ns 에 마운트하면 현재 네임스페이스에서만 마운트된것을 알 수 있다.
# tmpfs : 가상 메모리 파일시스템
# ushare 된 마운트 스페이스(격리됨)에서 생성한 마운트는 호스트에서 보이지 않는다.
mkdir /tmp/mount_ns
mount -n -t tmpfs tmpfs /tmp/mount_ns
mount | grep mount_ns

# 터미널을 하나 더 띄워 호스트 네임스페이스에서 보면 값이 다른것을 알 수 있다.
readlink /proc/$$/ns/mnt

# 호스트에서 tmpfs 마운트 정보가 안보이는것을 확인
mount | grep mount_ns

# 테스트 결과


#####


# pid namespace
# 새로운 pid(-p, --pid), -f(--fork) 네임스페이스으로 실행
# --mount-proc Just before running the program, mount the proc filesystem at mountpoint (default is /proc)
# --mount-prod 옵션을 사용하면 pid 네임스페이스가 /proc/{pid} 로 마운트되어 쉽게 pid 네임스페이스 접근할 수 있도록 해준다.(mount namspace 도 생긴다.)
unshare -fp --mount-proc /bin/sh

# 호스트 네임스페이스와 현재 pid 네임스페이스에 다른 pid 로 보인다.
# 현재 pid 네임스페이스에서 프로세스를 확인해보면 1번이다.
echo $$
ps -ef

# pid 네임스페이스에서의 pid namespace 정보를 보면
# 아래 호스트의 unshare mount namespace 와 다른것을 알 수 있다.
# unshare(mt ns id:4026532202) ---> pid-namespace /bin/sh (pid ns id:4026532203)
lsns -t pid -p 1

# 호스트에서 unshare 프로세스 pid 찾기
ps -ef | grep unshare

# ushare 프로세스의 namespace 정보
# ls -ahl /proc/{pid}/ns 로도 알 수 있다.
lsns -p {pid}

# mnt 네임스페이스ID 로 확인
lsns {namespace id}

# 테스트 결과 화면


#####


# cgroup namespace
# cgroup 는 controlgroup으로 프로세스의 리소스 제어하는 파일시스템이다.(sys/fs/cgroup)
# sleep 백그라운드 프로세스를 freezer(cgroup 작업 중지,재개를 위한 subsystem)의 sub2로 등록
mkdir -p /sys/fs/cgroup/freezer/sub2
sleep 100000 &
[1] 2932

# 테스트를 위해 sleep pid 환경변수로 설정 
export sleep_pid=2932

# sleep pid 를 sub2 cgroup 으로 등록
echo ${sleep_pid} > /sys/fs/cgroup/freezer/sub2/cgroup.procs

# 현재 프로세스도 freezer sub1 로 등록하자.
mkdir -p /sys/fs/cgroup/freezer/sub1

# 현재프로세스 확인, 테스트를 위해 현재 pid 환경변수로 설정 
echo $$
2178
export parent_pid=$(echo $$)

# 현재 프로세스 pid 를 sub1 cgroup 으로 등록
echo ${parent_pid} > /sys/fs/cgroup/freezer/sub1/cgroup.procs

# freezer (init process, PID 1), parent, sleep pid 확인
cat /proc/1/cgroup | grep freezer
cat /proc/${parent_pid}/cgroup | grep freezer
cat /proc/${sleep_pid}/cgroup | grep freezer

# freezer 파일 확인
tree -L 1 /sys/fs/cgroup/freezer/
/sys/fs/cgroup/freezer/
├── cgroup.clone_children
├── cgroup.procs
├── cgroup.sane_behavior
├── docker
├── notify_on_release
├── release_agent
├── sub1
├── sub2
└── tasks

# 새로운 cgroup(-C, --cgroup) 네임스페이스로 실행
unshare -Cm /bin/sh

# freezer (init process, PID 1), parent, sleep pid 확인해보면
# sub1 --> / 가 루트가 되고
# sub2 --> ../sub2 로 변경되었지만 호스트 cgroup 은 그대로 보인다.
cat /proc/1/cgroup | grep freezer
cat /proc/2178/cgroup | grep freezer
cat /proc/2932/cgroup | grep freezer

# 테스트 결과


#####


# stress 툴로 시스템 부하(cpu)를 테스트해보자.
# new_root 에 bash 준비하기
mkdir -p new_root/{bin,usr,lib,lib64} new_root/lib/x86_64-linux-gnu/ new_root/usr/lib/x86_64-linux-gnu
cp /bin/bash new_root/bin
cp /bin/mount new_root/bin
cp /lib/x86_64-linux-gnu/{libtinfo.so.5,libdl.so.2,libc.so.6} new_root/lib
cp /lib64/ld-linux-x86-64.so.2 new_root/lib64

# mount 명령등 준비하기
cp -rv /bin/{ls,cat,mount,umount,mkdir,rmdir,rm,cp,grep,ps} new_root/bin
cp -rv /usr/lib/x86_64-linux-gnu/* new_root/usr/lib/x86_64-linux-gnu
cp -rv /lib/x86_64-linux-gnu/* new_root/lib/x86_64-linux-gnu
cp -rv /lib64/* new_root/lib64

# stress, cgroup 툴 설치
apt-get install -y stress cgroup-tools

# chroot 후 필요한 파일들 복사
mkdir -p new_root/usr/bin/bin
cp /usr/bin/{top,stress,cgcreate,cgset,cgexec} new_root/usr/bin/

# top 실행시 필요한 파일들 복사
mkdir -p new_root/usr/share/terminfo
cp -r /usr/share/terminfo/ new_root/usr/share/terminfo

# cgcreate 시 root uid 파악에 필요
mkdir -p new_root/etc
cp /etc/group new_root/etc/group
cp /etc/passwd new_root/etc/passwd

# cgroup 관련 마운트 확인
mount | grep -E "cgroup*.cpu"

# chroot 후 mount 하기
chroot new_root
mkdir -p /sys/fs/cgroup
mkdir -p /sys/fs/cgroup/{cpuset,"cpu,cpuacct"}
mount -t cgroup -o cpuset cgroup /sys/fs/cgroup/cpuset
mount -t cgroup -o cpu,cpuacct cgroup /sys/fs/cgroup/cpu,cpuacct

# /proc 마운트
mkdir -p /proc
mount -t proc proc /proc

# mycgroup 생성 및 확인
cgcreate -a root -g cpu:mycgroup
ls -al /sys/fs/cgroup/cpu,cpuacct/ | grep mycgroup

# CPU 사용률(%) = (cpu.cfs_quota_us / cpu.cfs_period_us) * 100
# cpu.cfs_period_us 를 확인해 보면 100000(us, 100ms) 이다.
# cat /sys/fs/cgroup/cpu,cpuacct/cpu.cfs_period_us
# 따라서 cpu.cfs_quota_us=30000 을 주면 30% cpu 사용률로 제한한다.
cgset -r cpu.cfs_quota_us=30000 mycgroup

# mycgroup 에 strecc -c(--cpu) 1로 부하 테스트 백그라운드로 실행
cgexec -g cpu:mycgroup stress -c 1 &

# top 으로 stress 프로세스 30% 사용 확인
export TERM="xterm-256color"
top
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 3129 root      20   0    8248     96      0 R  30.0  0.0   0:03.50 stress

# 테스트 결과


#####


# net namespace
# 새로운 network(-n, --net) 네임스페이스으로 실행
unshare --net /bin/sh

# 호스트 네임스페이스와 정보가 다른것을 알 수 있다.
ip a

# 테스트 결과

# ip 로 net namespace 생성해보기
ip netns add mynet

# net namespace 확인
ip netns list
# 또는
ls /var/run/netns

# nsenter 로 net네임스페이스(--net)에 들어가기
# 별도 프로그램 명시하지 않으면 디폴트로 $SHELL -> /bin/bash 를 실행한다.
nsenter --net=/var/run/netns/mynet
ip a

# net 네임스페이스에서 현재 프로세스의 pid,user,ipc,mnt등의 네임스페이스 정보 확인
lsns -p $$

# 호스트에서 net 네임스페이스 id 확인
# readlink /proc/$$/ns/net 대신 lsns 명령어로도 네임스페이스 정보를 확일 수 있다.
# lsns -t 타입 -p pid
lsns -t net -p $$

# 테스트 결과


#####


# user namespace 생성
# 새로운 user(-U, --user) 네임스페이스으로 실행
unshare --user /bin/sh

# 새로운 user 네임스페이스에서의 사용자 정보 확인
whoami
id

# 네임스페이스 확인해보기
ls -al /proc/$$/ns
lsns -p $$

# 테스트 결과 화면

# container 와 호스트 root 가 같은것인지 확인해보기
# docker 로 busybox 컨테이너 띄우기
docker container run -it --name busybox --rm busybox

# ps 로 보면 root 로 실행됐고, uid=0 으로 되어 있다
ps -ef
id

# 현재 프로세스의 user 네임스페이스 확인
readlink /proc/$$/ns/user

# 호스트에서도 확인해보면 root 이름이고 uid=0 이다.
ps -ef | grep root | head -3
id

# 호스트에서 현재 프로세스의 user 네임스페이스 확인해보면 위 container user namespace id 와 같은것을 알 수 있다.
# 결국 container 의 root 도 호스트 user 네임스페이스를 공유하기 때문에 호스트 root 와 같은것이다.
readlink /proc/$$/ns/user

# 테스트 결과 화면

# 참고로 docker v1.10 이상 부터는 별도의 user namespace 를 지원하지만
기본적으로 docker 로 컨테이너를 실행하면 root 로 프로세스를 실행한다.(user namespace 를 별도로 만들지 않고 호스트를 user namespace 를 사용한다.)
# 이 때문에 container root 계정이 악용될 수 있음을 인지해야 한다.
# docker remap 설정으로 uid 를 다르게할 수 있다. 아래 별도 포스트로 정리


#####


# uts(unix time sharing system) namespace
# 새로운 uts(-u, --uts) 네임스페이스으로 실행
unshare --uts /bin/sh

# 호스트명 변경하고 확인
hostname ysoftman
hostname

# 터미널을 하나 더 띄워 호스트를 확인해 보면 영향을 주지 않는것을 알 수 있다.
# 테스트 화면


#####


# ipc(interprocess communication) namespace
# shared memory, queue, semaphore 등의 리소스에서 사용되며 같은 ipc 네임스페이스안 프로세스들은 ipc 통신할 수 있다.
# ipc (shared memory, pipe) 상태 정보 보기
# -m  Write information about active shared memory segments.
ipcs -m

# ipc shared memeory 리소스 1000byte 생성
# -m Create a shared memory segment of size bytes.
ipcmk -M 1000

# 1000 byte shared memory segemnt 가 생성된것을 확인할 수 있다.
ipcs -m

# 새로운 ipc(-i, --ipc) 네임스페이스 생성
unshare --ipc /bin/sh

# 새로운 ipc 네임스페이스에서는 호스트에서 만든 1000bytes 리소스가 보이지 않는다.
ipcs -m

# 호스트로 나오기
exit

# ipc 리소스 삭제
# -M Remove the shared memory segment created with shmkey after the last detach is performed.
ipcrm -M {shared memory segment key}

# 삭제된 리소스 확인 
ipcs -m

# 위 과정을 테스트한 결과

chroot, pvirot_root, unshare

# container 없던 시절 완벽하진 않은 격리효과를 위해서 사용할 수 있는 명령들

# 가상작업환경 : virtualbox + vagrant + ubuntu box image
# vagrant up 후 vagrant ssh 로 접속
# root 환경에서 작업하기
sudo -Es

# 마운트등의 이슈로 /tmp 에서 작업한다.
cd /tmp

# chroot 사용하기
# 새로운 root 경로가 될 디렉토리 생성
mkdir new_root

# 단순히 chroot new_root 하면
chroot new_root
# 다음과 같은 에러가 발생한다.
chroot: failed to run command ‘/bin/bash’: No such file or directory

# chroot 하면 bash 가 실행하는데 new_root 에서는 bin/bash 가 없어 이를 새루트에 복사해야 한다.
# bash 구동을 위해 필요한 파일들 확인하자
ldd /bin/bash
linux-vdso.so.1 (0x00007ffe49bfb000)
libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007fc6e4d28000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fc6e4b24000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fc6e4733000)
/lib64/ld-linux-x86-64.so.2 (0x00007fc6e526c000)

# bash 및 관련 라이브러리 복사
mkdir -p new_root/{bin,usr,lib,lib64} new_root/lib/x86_64-linux-gnu/ new_root/usr/lib/x86_64-linux-gnu
cp /bin/bash new_root/bin
cp /lib/x86_64-linux-gnu/{libtinfo.so.5,libdl.so.2,libc.so.6} new_root/lib
cp /lib64/ld-linux-x86-64.so.2 new_root/lib64

# ls 외 필요한 기본 명령어도 확인하고 복사하자.
# ps 는 /proc 디렉토리를 생성(mkdir)하고 mount 해야 사용할 수 있다.
ldd /bin/ps
linux-vdso.so.1 (0x00007ffc403b6000)
libprocps.so.6 => /lib/x86_64-linux-gnu/libprocps.so.6 (0x00007f594c99d000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f594c799000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f594c3a8000)
libsystemd.so.0 => /lib/x86_64-linux-gnu/libsystemd.so.0 (0x00007f594c124000)
/lib64/ld-linux-x86-64.so.2 (0x00007f594ce03000)
librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007f594bf1c000)
liblzma.so.5 => /lib/x86_64-linux-gnu/liblzma.so.5 (0x00007f594bcf6000)
liblz4.so.1 => /usr/lib/x86_64-linux-gnu/liblz4.so.1 (0x00007f594bada000)
libgcrypt.so.20 => /lib/x86_64-linux-gnu/libgcrypt.so.20 (0x00007f594b7be000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f594b59f000)
libgpg-error.so.0 => /lib/x86_64-linux-gnu/libgpg-error.so.0 (0x00007f594b38a000)

# 일일히 lib 복사하지 말고 그냥 lib,lib64 파일 모두 복사해놓자.
cp -rv /bin/{ls,cat,mount,umount,mkdir,rmdir,rm,cp,grep,ps} new_root/bin
cp -rv /usr/lib/x86_64-linux-gnu/* new_root/usr/lib/x86_64-linux-gnu
cp -rv /lib/x86_64-linux-gnu/* new_root/lib/x86_64-linux-gnu
cp -rv /lib64/* new_root/lib64

# 이제 chroot 를 해보면 /bin/bash 가 실행된다.
chroot new_root
# root 경로가 바뀐것 확인(cat,mkdir,ps 등도 동작한다.)
ls /

# ps 명령을 실행하기 위해선 /proc 디렉토리를 만들고 마운트해야한다.
mkdir -p /proc
mount -t proc proc /proc

# chroot 는 다음코드를 실행하면 현재 root 경로를 나갈수 있다(탈옥가능)
cat > break_chroot.c << EOF
#include <sys/stat.h>
#include <unistd.h>
int main(void)
{
    mkdir("foo", 0755);
    chroot("foo");
    chdir("../../../../../");
    chroot(".");
    return execl("/bin/bash", "-i", NULL);
}
EOF
# 빌드
gcc break_chroot.c -o break_chroot
# new_root 에 복사하고
cp -v break_chroot new_root
# new_root 에서 실행하면 chroot 의 / 를 빠져나가는 이슈가 있다.
chroot new_root
./break_chroot

# chroot 나가기
exit


#####


# pivot_root : root 를 new_root 아래로 mount 하여 경로 탈옥을 막아보자.

# 새로운 root 경로가될 디렉토리 생성
mkdir new_root2

# unshare 는 부모로 부터 격리된 특정 네임스페이스로 프로그램 실행�한다.
# -m : unshare mount namespace
# 프로그램 명시하지 않으면 기본 $SHELL(/bin/sh 등) 를 실행한다.
unshare -m

# 새로운 파일시스템을 생성해 new_root2 에 마운트
# 참고로 
# chroot 테스트에서 사용했던 파일들 모두 사용하기 위해 디스크 크기를 넉넉하게 한다.
# 디스크 크기를 너무 적게 설정하면 mount 오류로 다음과 같은 에러가 발생할 수 있다.
# -bash: /bin/ls: Input/output error
mount -t tmpfs -o size=500M none new_root2

# chroot 테스트에서 사용했던 파일들 복사해오기
cp -rv new_root/* new_root2/

# root 경로를 마운트할 디렉토리 생성
mkdir new_root2/put_old
cd new_root2

# pivot_root: change the root filesystem
# 현재 프로세스의 root 파일시스템을 put_old 로 옮기고 . 를 새로운 root 파일 시스템으로 만든다.
pivot_root . put_old

# root 파일 시스템 자체가 바뀌었음으로
# chroot 탈옥 코드로 root 경로 탈옥이 되지 않는다.
./break_chroot

# unshare 나가기
exit