# 컨테이너를 구성하는 리눅스

안규원·2024년 9월 3일
0

Infra

목록 보기
19/23
post-thumbnail

리눅스 배포판


Debian 계열: Ubuntu
오픈소스이며 안정성
apt-get/apt

Red hat/Fedora 계열: Fedora, CentOS, Amazon Linux
yum

openSUSE 계열

리눅스 커널


오픈 소스 모놀리딕 유닉스 계열 컴퓨터 OS 커널
크로스 플랫폼: 다양한 컴퓨터 아키텍처 지원


컴퓨터 운영 체제의 핵심이 되는 컴퓨터 프로그램
시스템의 모든 것을 완전히 통제
어플리케이션과 CPU/Memory/Devices 연결


하드웨어 관리 및 추상화
디바이스 드라이버만이 하드웨어가 제공하는 기능이나 프로토콜에 관여
사용자 프로그램은 하드웨어 종속적인 작업이 필요하지 않음


프로세스와 쓰레드 관리
CPU타임 공유와 메모리 보호


메모리 관리
개별 프로세스에 가상의 연속된 메모리 공간 제공
물리 메모리보다 큰 크기의 프로그램이 동시에 실행 가능(페이징)


I/O관리
하부 시스템 구성에 상관없이 파일 입출력 형식으로 제어 가능(VFS:Virtual File System)

로컬 리눅스 설치


  • host 환경에 직접 설치(HW - Host OS): 기존 윈도우/맥은 불가
  • 가상환경에 설치(HW - Hypervisor - VM - Guest OS)
  • 가상환경에 설치(HW - Host OS - Hypervisor - VM - Guest OS): VirtualBox 이용
  • 컨테이너 환경(HW - Host OS - Container Runtime - Container)

VitrualBox: Type-2 형태의 하이퍼바이저

NAT: NAT를 통해 가상머신이 인터넷 망 접속, 가상머신 간 통신 불가
NAT Network: 호스트 간 네트워크를 공유, 가상머신 간 통신 가능
Bridged Adapter: 호스트 머신과 동일한 네트워크 사용


Vargrant: 포터블한 가상 소프트웨어 개발 환경의 생성/유지보수를 위한 오픈소스 소프트웨어

# window powershell(관리자권한)에서 윈도우용 패키지(프로그램)관리 프로그램 chocolatey 설치
$ Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))

# virtualbox 설치
$ choco install virtualbox

# vargrant 설치
$ choco install vagrant

# powershell 재실행 후 vargrant 설치 확인
$ vargrant

# Ubuntu 설치 디렉터리 생성
$ mkdir ubuntu && cd ubuntu

# 설정파일 생성
$ vargrant init centos/7

# 가상 os 실행
$ vargrant up --provider=virtualbox

# 터미널 접속
$ vargrant ssh

Vargrant 명령어

# vagrant 설정파일 VagrantFile파일 생성
$ vagrant init 

# Vagrantfile을 기반으로 가상 운영체제 실행
$ vagrant up 	

# 실행한 가상운영체제 종료
$ vagrant halt 	

# 설치한 가상운영체제 box 삭제 
$ vagrant destroy	

클라우드 리눅스


지금까지 작업했던 ec2 인스턴스를 이용

SSH Client(local) -> SSH Server(AWS remote)

VS Code Client(local OS) -> VS Code Server(AWS Remote)

VS Code/Remote-SSH 플러그인 설치 후 접속(호스트 OS의 .ssh에 key파일 copy)

이후 동일하게. docker/git 등 설치후 환경 구성

$ sudo apt install -y git

$ git clone {url}

cgroup


컨테이너를 구성하는 3가지 주요 리눅스 기술

  • Control groups
  • Namespaces
  • Union mount filesystem

cgroups(Control Groups)
프로세스들이 사용하는 시스템 자원의 사용정보를 수집 및 제한시키는 리눅스 커널 기능

제한 대상: CPU, Memory, Network, Device, Block I/O

사용 가능한 서브시스템

  • cpu: 스케줄러를 이용해 프로세스의 CPU 사용시간 제어
  • memory: 해당 cgroup에 속한 프로세스의 메모리 사용량 제어
  • freezer: cgroup의 작업을 일시 중지하거나 다시 시작

Core workload/Non-core services/Ad-hoc에 따라 다르게 관리


Anti Virus앱 CPU사용 제한

stress tool로 cpu로드 생성 -> CPU 사용량 10% 제한

# stress tool 설치
$ sudo apt install -y stress

# root
$ sudo su

# 작업을 위한 디렉토리 생성
$ cd /sys/fs/cgroup

# 그룹으로 이동해 현재 세션을 해당 그룹에 추가
$ mkdir utils && cd utils

# 터미널 자신의 프로세스ID 확인
$ echo $$

1665

# 현재 관리대상은 없음
$ cat cgroup.procs

# 현재 터미널의 프로세스ID를 관리대상으로편입 
$ echo $$ > cgroup.procs

# 현재 터미널의 프로세스ID가 관리대상으로 편입되었음
$ cat cgroup.procs

1665
2105

# period 값 확인
$ cat cpu.max

max 100000

# max 설정
$ echo 10000 > cpu.max

# 10,000 / 100,000 = 10% 확인
$ cat cpu.max

10000 100000

# stress test 실행
$ stress -c 1

stress: info: [2551] dispatching hogs: 1 cpu, 0 io, 0 vm, 0 hdd

# 커맨드 분할해 확인
$ top

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ 
   2552 root      20   0    3620    384    384 R  10.0   0.0   0:24.82 
   1217 ubuntu    20   0 1262996  94984  45824 S   0.3   9.7   0:04.87 
   1254 ubuntu    20   0 1203460  65676  41216 S   0.3   6.7   0:02.21 
   1465 ubuntu    20   0   15364   7320   4992 S   0.3   0.7   0:00.63
   
# 다른 쉘로도 확인
$ echo 20000 > cpu.max

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ 
   4963 root      20   0    3620    384    384 R  20.0   0.0   0:11.66 
   1217 ubuntu    20   0 1263764  95372  45824 S   0.3   9.7   0:05.58 
   1254 ubuntu    20   0 1269916  65700  41216 S   0.3   6.7   0:03.59

fork bomb 방어


프로세스가 지속적으로 자신을 복제함으로써 시스템 자원을 고갈시키고, 속도를 떨어트려 충돌을 일으키는 서비스 거부 공격

# cgroup 디렉토리 이동
$ cd /sys/fs/cgroup

# 예제 디렉토리 생성 후 진입. 디렉토리명과 상관없이 내부에는 cgroup config파일이 생성됨(크기는 0)
$ mkdir police && cd police

# 터미널 프로세스를 관리대상으로
$ echo $$ > cgroup.procs

# 프로세스 생성 개수 확인
$ cat pids.max
max

# 최댓값 수정
$ echo 5 > pids.max

# fork bomb 실행
$ :(){ :|:& };:

# 다른 쉘에서 생성 프로세스 확인
$ ps aux | grep "0:00 bash" | wc -l

ubuntu      1373  0.0  0.3   7872  3584 ?        Ss   05:23   0:00 bash
root        1740  0.0  0.4   8132  4224 pts/2    S+   05:23   0:00 bash
root        8927  0.0  0.2   8132  2556 pts/2    S    05:33   0:00 bash
root        8994  0.0  0.2   8132  2560 pts/2    S    05:33   0:00 bash
root        8996  0.0  0.2   8132  2560 pts/2    S    05:33   0:00 bash
root        8997  0.0  0.2   8132  2560 pts/2    S    05:33   0:00 bash
root        8998  0.0  0.2   8132  2560 pts/2    S    05:33   0:00 bash
root        8999  0.0  0.2   8132  2560 pts/2    S    05:33   0:00 bash
root        9209  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9231  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9232  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9244  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9245  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9246  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9247  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9248  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash
root        9250  0.0  0.2   8132  2560 pts/2    S    05:34   0:00 bash

20개 가량

namespace

프로세스별로 별도의 커널 자원을 분할하는 리눅스 커널의 기능으로 리눅스 컨테이너 기술의 근간

격리대상: 프로세스ID, Network, File system, user정보 등

cgroup how much you can use
namespaces what you can see

  • PID 네임스페이스: Process ID정보 격리, 네임스페이스 외의 다른 프로세스에 접근 불가능
  • Network 네임스페이스: 네트워크 장치, IP주소, 포트, 라우팅 테이블 등의 네트워크 리소스 격리
  • User 네임스페이스: 프로세스 별 UID, GID 정보 격리
  • Mount 네임스페이스: 프로세스별로 마운트되는 파일시스템 격리

Mount 네임스페이스

새로운Mount 네임스페이스에서 마운트한 위치에 파일 생성

# unshare 명령어 확인
$ man unshare

# 프로세스 확인
$ echo $$
29687

# 새로운 mount namespace 생성
$ unshare -m /bin/bash

# 새로운 쉘 확인
$ echo $$
32123

# 프로세스 확인
$ ps auxf

# /mnt에 tmpfs(메모리에 생성되는 가상 파일 시스템) 마운드하여 임시 스토리지로 사용
$ mount -t tmpfs tmpfs /mnt

$ cd /mnt

$ echo "Hello" > hello.txt

# 새로운 터미널에서 확인하면 namespace가 다르기 때문에 해당파일 접근할 수 없음. 따라서 해당 namespace PID로 접근
$ nsenter -t 32123 -a
$ ls -l /mnt
total 4
-rw-r--r-- 1 root root 6 Sep  4 06:03 hello.txt

PID 네임스페이스

PID 네임스페이스 생성 후 프로세스 트리 정보 확인, 독립된 프로세스 환경임을 확인

$ echo $$
29687

# 새로운 PID namespace 생성(순서대로 PID, mount, IPC, child process)
$ unshare -pmif

$ echo $$
1

# 1번 프로세스이므로 상위 5개 확인
$ ps aux | head -n5

USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.1  1.3  22096 13204 ?        Ss   05:21   0:04 /sbin/init
root           2  0.0  0.0      0     0 ?        S    05:21   0:00 [kthreadd]
root           3  0.0  0.0      0     0 ?        S    05:21   0:00 [pool_workqueue_release]
root           4  0.0  0.0      0     0 ?        I<   05:21   0:00 [kworker/R-rcu_g]

# 해당 정보는 profile 시스템에서 읽어야 하므로 새로운 namespace에 맞는 proc filesystem 마운트

$ mount -t  proc none /proc
$ ls /proc

# 프로세스 재확인
$ ps aux | head -n5
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.5   9192  5376 pts/4    S    06:19   0:00 -bash
root          17  0.0  0.4  11320  4352 pts/4    R+   06:24   0:00 ps aux
root          18  0.0  0.1   6124  1792 pts/4    S+   06:24   0:00 head -n5

Network 네임스페이스

새로운 Network 네임스페이스 생성 후 생성된 네임스페이스 간의 네트워크 연결 확인(Docker/Bridge 형태)

VETH(Virtual Ethernet Pair): 로컬 이더넷 터널로 네트워크 네임스페이스 간 연결 가능하게 하며 페어로 구성됨

CETH(Container Ethernet Interface): 컨테이너 내의 가상 네트워크 인터페이스

$ sudo su

# network namespace 생성
$ ip netns add ns0
$ ip netns add ns1

$ ip netns list
ns0
ns1

# net namespace 안에 어떤 link 있는지 확인
$ ip link # root
$ ip netns exec ns0 ip link # ns0

# network namespace 내의 인터페이스 확인(ns0 에서 실행됨)
$ ip netns exec ns0 ip link
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

# 구동
$ ip netns exec ns0 ip link set lo up
$ ip netns exec ns1 ip link set lo up
    
# bridge 확인
$ ip link show type bridge

# 호스트에 새로운 브릿지 네트워크 br0 생성 후 구동
$ ip link add br0 type bridge
$ ip link set br0 up
$ ip link show type bridge
4: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default qlen 1000
    link/ether d2:38:95:8f:5b:e3 brd ff:ff:ff:ff:ff:ff
    
# 브릿지 네트워크에 IP 설정  
$ ip addr add 192.168.2.1/24 dev br0
$ ip addr
4: br0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default qlen 1000
    link/ether d2:38:95:8f:5b:e3 brd ff:ff:ff:ff:ff:ff
    inet 192.168.2.1/24 scope global br0
       valid_lft forever preferred_lft forever
       
# 확인
$ ping -c 2 192.168.2.1
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=0.029 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=0.033 ms

--- 192.168.2.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1045ms
rtt min/avg/max/mdev = 0.029/0.031/0.033/0.002 ms

# veth 페어 생성
$ ip link add veth0 type veth peer name ceth0
$ ip link add veth1 type veth peer name ceth1

# veth를 master br0에 연결&구동
$ ip link set veth0 master br0
$ ip link set veth1 master br0

$ ip link set veth0 up
$ ip link set veth1 up

# ceth를 namespace에 연결&구동
$ ip link set ceth0 netns ns0
$ ip link set ceth1 netns ns1

$ ip netns exec ns0 ip link set ceth0 up
$ ip netns exec ns1 ip link set ceth1 up
    
# 구동 확인
$ ip netns exec ns1 ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
7: ceth1@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 7a:06:0f:4e:51:72 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    
# IP가 없으므로 지정
$ ip netns exec ns0 ip addr add 192.168.2.2/24 dev ceth0
$ ip netns exec ns1 ip addr add 192.168.2.3/24 dev ceth1

# IP 확인
$ ip netns exec ns0 ip addr
5: ceth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 8e:20:ca:4e:dd:ed brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.2.2/24 scope global ceth0
       valid_lft forever preferred_lft forever
    
# 네임스페이스 간 통신 확인
$ ip netns exec ns0 ping -c 2 192.168.2.3

!!!!!!!!!!!!!!!!!!!!!!!!
# FORWARD 정책 확인
$ iptables -L FORWARD
Chain FORWARD (policy DROP 16 packets, 1344 bytes)

# DROP이므로 ACCEPT로 바꿔줘야함
$ iptables -P FORWARD ACCEPT
Chain FORWARD (policy ACCEPT)
!!!!!!!!!!!!!!!!!!!!!!!!

union mount filesystem

하나의 디렉토리 위치에 파일 시스템을 마운트하면?

$ mount -t tmpfs tmpfs /home/ubuntu/linux_campus

$ mount | grep tmpfs
tmpfs on /home/ubuntu/linux_campus type tmpfs (rw,relatime,inode64)

$ ls -l linux_campus/
total 0

$ umount /home/ubuntu/linux_campus

$ ls -l linux_campus/
total 20
drwxrwxr-x  4 ubuntu ubuntu 4096 Sep  3 15:46 Part2

Union mount: 하나의 디렉토리 위치에 여러개의 디렉토리를 마운트하면 하나의 통합된 디렉토리처럼 보이게 하는 방법

Image Layer: 마지막 레이어를 제외하고 Read-Only
CoW(Copy-on-write): 변경된 파일만 저장

Docker 적용? 스토리지 드라이버 e.g. overlay
Overlay FS: 하나의 파일 시스템을 다른 파일 시스템 상단에 overlay


OverylayFS로 Union mount

lower1, lower2, upper(writable), merged(실제 사용), work(file system에서 관리목적)

$ mkdir /tmp/{lower1,lower2,upper,merged,work}

$ echo "lower1 a" > /tmp/lower1/a.txt
$ echo "lower1 b" > /tmp/lower1/b.txt
$ echo "lower2 a" > /tmp/lower2/a.txt
$ echo "lower2 c" > /tmp/lower2/c.txt

# read only overlay(/tmp/merged)
$ mount -t overlay overlay overlay -o lowerdir=/tmp/lower1:/tmp/lower2 /tmp/merged

$ mount | grep overlay
overlay on /tmp/merged type overlay (ro,relatime,lowerdir=/tmp/lower1:/tmp/lower2,redirect_dir=on,nouserxattr)

$ cat /tmp/merged/a.txt
lower1 a

$ echo "hello ro" > /tmp/merged/d.txt
bash: /tmp/merged/d.txt: Read-only file system

$ umount /tmp/merged

# Read/Write 갖는 uppder지정
$ mount -t overlay overlay -o lowerdir=/tmp/lower1:/tmp/lower2, upperdir=/tmp/upper,workdir=/tmp/work /tmp/merged

# RW 확인
$ mount | grep overlay
overlay on /tmp/merged type overlay (rw,relatime,lowerdir=/tmp/lower1:/tmp/lower2,upperdir=/tmp/upper,workdir=/tmp/work,uuid=on,nouserxattr)

$ echo "Hello World" > /tmp/merged/hello.txt

$ cat /tmp/merged/hello.txt
Hello World

$ ls -l /tmp/upper
total 4
-rw-r--r-- 1 root root 12 Sep  4 09:07 hello.txt

$ umount /tmp/merged

# busy는, 해당 디렉토리에 들어가 있어서 발생함. 빠져나와서 umount

docker union mount test

$ docker pull nginx

# 데몬으로 실행
$ docker run --rm -d nginx

$ mount | grep overlay

# rw, lowerdir에 여러 디렉토리 지정됨을 확인, uppderdir에 하나의 디렉토리 지정 확인, workdir 지정 확인 

0개의 댓글