25/10/22 컨테이너 기술과 애플리케이션 가상화 4

344th·2025년 12월 11일

AWS AI

목록 보기
32/48

도커 네트워크

  • 게스트 환경
  • 개발 환경
  • 운영 환경

네트워크 생성(개발, 운영)

$ docker network create dev-network
0f2960e0ead0eb7ad31fc6b75e5450ff7ead9e704aac41278fa6b26ac9e8d58e

$ docker network create prod-network
8517dea65d5448b6d81b7d3e4658de43c923740ebc29116e38183c279f364114

컨테이너 생성(개발, 운영)

$ docker run -d --name dev-db --network dev-network -p 3306:3306 -e MYSQL_ROOT_PASSWORD=1234 mysql:5.7
6bee6724625debc9230546f87022963988a8317929c3e29b67f2eccc3d8fc39e

$ docker run -d --name prod-db --network prod-network -p 3307:3306 -e MYSQL_ROOT_PASSWORD=1234 mysql:5.7
fec3ad1f7613767511345bf45205982122c5fa208b5ddae9303801f235310a0c

개발 환경, 운영 환경 접속 후 데이터베이스 생성 및 데이터 삽입

# db 생성
$ docker exec dev-db mysql -uroot -p1234 -e "create database appdb;"
$ docker exec prod-db mysql -uroot -p1234 -e "create database appdb;"

# 테이블 생성
$ docker exec dev-db mysql -uroot -p1234 -e "create table appdb.users (id INT, name VARCHAR(50));"
$ docker exec prod-db mysql -uroot -p1234 -e "create table appdb.users (id INT, name VARCHAR(50));"

# 테이블에 데이터 삽입
$ docker exec dev-db mysql -uroot -p1234 -e "insert into appdb.users values (1, 'from dev');"
$ docker exec prod-db mysql -uroot -p1234 -e "insert into appdb.users values (1, 'from prod');"

확인

**$ docker exec dev-db mysql -uroot -p1234 -e "select * from appdb.users;"**
mysql: [Warning] Using a password on the command line interface can be insecure.
id      name
1       from dev

**$ docker exec prod-db mysql -uroot -p1234 -e "select * from appdb.users;"**
mysql: [Warning] Using a password on the command line interface can be insecure.
id      name
1       from prod

네트워크 테스트

**/ # ping -c 3 dev-db**
PING dev-db (172.23.0.2): 56 data bytes
64 bytes from 172.23.0.2: seq=0 ttl=64 time=0.336 ms
64 bytes from 172.23.0.2: seq=1 ttl=64 time=0.155 ms
64 bytes from 172.23.0.2: seq=2 ttl=64 time=0.144 ms

--- dev-db ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.144/0.211/0.336 ms

# 운영환경에 보내면 다른 네트워크에 있으므로 당연히 가지 않음
**/ # ping -c 3 prod-db**
ping: bad address 'prod-db'

Dockerfile

도커 파일 지시어

ARG

ARG: 빌드 변수 정의

개발 환경과 운영 환경의 빌드 베이스 이미지를 다르게 하고 싶을 때

개발 환경 빌드

**$ vi Dockerfile**

ARG BASE_VERSION=9
FROM public.ecr.aws/docker/library/rockylinux:
${BASE_VERSION}

RUN yum install -y httpd


**$ docker build -t myapp:dev .**

운영 환경 빌드(베이스 이미지 8로 지정)

$ docker build --build-arg BASE_VERSION=8 -t myapp:prod .

확인

**$ docker images | grep myapp**
myapp            prod      c756cc928ed4   About a minute ago   323MB
myapp            dev       3f788429af27   3 hours ago          234MB

LABEL

LABEL: 이미지에 메타데이터 추가

**$ vi Dockerfile**

FROM rockylinx:9

LABEL maintainer="minseop@example.com" \
version="1.0" \
description="라파 웹서버" \
team="인프라팀"

빌드

$ docker build -t myapp:1.0 .

인스펙트

$ docker inspect myapp:1.0 | grep -A 5 Labels
            "Labels": {
                "description": "라파 웹서버",
                "maintainer": "dltnals1210@gmail.com",
                "team": "인프라팀",
                "version": "1.0"
            },

HEALTHCHECK

FROM rockylinux:9
RUN yum install -y httpd
COPY index.html /var/www/html/index.html

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
CMD curl -f http://localhost/ || exit1

CMD ["/usr/sbin/httpd", "-DFOREGROUND"]


docker build -t webapp:health .
docker run -d --name web webapp:health

docker exec web rm -f /var/www/html/index.html
watch -n1 docker ps
**$ docker exec web pkill httpd**
OCI runtime exec failed: exec failed: unable to start container process: exec: "pkill": executable file not found in $PATH: unknown

pkill 기본적으로 설치 안돼있음

$ docker exec web yum install -y procps-ng

설치

$ docker exec web pkill httpd

web 컨테이너의 httpd 프로세스 kill

web 컨테이너의 CMD(메인 프로세스, PID 1)인 httpd 프로세스가 종료됐기 때문에 컨테이너도 종료됨

**$ docker ps**
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES

새로 컨테이너 생성

$ docker run -d --name web-test webapp:health

컨테이너 내부 프로세스 확인

**$ docker top web-test**
UID                 PID                 PPID                C                   STIME               TTY                 TIME                CMD
root                65108               65083               0                   04:06               ?                   00:00:00            /usr/sbin/httpd -DFOREGROUND
48                  65129               65108               0                   04:06               ?                   00:00:00            /usr/sbin/httpd -DFOREGROUND
48                  65130               65108               0                   04:06               ?                   00:00:00            /usr/sbin/httpd -DFOREGROUND
48                  65131               65108               0                   04:06               ?                   00:00:00            /usr/sbin/httpd -DFOREGROUND
48                  65132               65108               0                   04:06               ?                   00:00:00            /usr/sbin/httpd -DFOREGROUND

원하는 정보가 다 나오지 않음

**$ docker exec web-test ps**
OCI runtime exec failed: exec failed: unable to start container process: exec: "ps": executable file not found in $PATH: unknown

ps -ef 로 확인하려고 하면 기본적으로 ps 가 설치 되어 있지 않음

**$ docker exec web-test cat /proc/1/cmdline**
/usr/sbin/httpd-DFOREGROUND

/proc/1/cmdline

: “컨테이너의 PID 1 프로세스가 실행 중인 실제 명령어”를 보여주는 파일

: 즉, “이 컨테이너가 어떤 명령으로 살아있는가”를 정확히 알려주는 리눅스 내부 스냅샷

USER

**$ vi myapp.sh**
#!/bin/bash
echo "현재 사용자: $(whoami)"
echo "현재 UID: $(id -u)"
sleep 1000

실행 권한 부여

$ chmod +x myapp.sh

도커 파일 작성

**$ vi Dockerfile**
FROM rockylinux:9
RUN yum install -y httpd

# 1. 일반 사용자 생성
RUN useradd -m -s /bin/bash appuser

# 2. 애플리케이션 디렉터리 권한 추가
WORKDIR /app
COPY myapp.sh /app/
RUN chown appuser:appuser /app

USER appuser

CMD ["./myapp.sh"]

이미지 빌드

$ docker build -t userapp .

컨테이너 생성 및 실행

$ docker run -d --name user-app userapp

컨테이너 실행시 myapp.sh 실행, 결과 확인

$ docker logs user-app
현재 사용자: appuser
현재 UID: 1000

로그로 확인

ADD

디렉터리, 파일 생성 및 압축

$ mkdir add

$ cd add/

$ echo "hello docker" > test.txt

$ ls
test.txt

$ tar -czf test.tar.gz test.txt

$ ls
test.tar.gz  test.txt

도커파일 복사

**$ vi Dockerfile.copy**
FROM rockylinux:9
WORKDIR /app
COPY test.txt /app/
COPY test.tar.gz /app/
CMD ["ls", "-la", "/app"]

**$ vi Dockerfile.add**
FROM rockylinux:9
WORKDIR /app
ADD test.txt /app/
ADD test.tar.gz /app/
CMD ["ls", "-la", "/app"]

카피 빌드

$ docker build -f Dockerfile.copy -t test:copy .

Dockerfile 이 아니므로 파일 따로 지정(-f)

카피 실행

**$ docker run --rm test:copy**
total 8
drwxr-xr-x. 1 root root  25 Oct 21 04:30 .
drwxr-xr-x. 1 root root   6 Oct 21 04:31 ..
-rw-r--r--. 1 root root 137 Oct 21 04:26 test.tar.gz
-rw-r--r--. 1 root root  13 Oct 21 04:25 test.txt

add 빌드 및 실행

$ docker build -f Dockerfile.add -t test:add .

$ docker run --rm test:add
total 4
drwxr-xr-x. 1 root root 22 Oct 21 04:34 .
drwxr-xr-x. 1 root root  6 Oct 21 04:34 ..
-rw-r--r--. 1 1000 1000 13 Oct 21 04:25 test.txt

test.tar.gz 파일이 없는 것을 확인 가능

왜?

ADD 는 압축을 알아서 풀어줌

COPY vs. ADD

실무에선 COPY 를 권장함

  1. 명확함: 그냥 복사만 하기 때문에 예측이 가능
  2. 가독성

도커 레이어

도커 이미지(image)
: “여러 개의 읽기 전용 파일시스템 스냅샷(=레이어)
이 겹쳐진 것

각 Dockerfile 명령어(FROM, RUN, COPY, ADD, …)가 한 줄씩 실행될 때마다

그 결과로 생긴 파일시스템의 변경사항(diff)하나의 레이어(layer) 로 저장됨

레이어는 수정될 수 없음(읽기 전용)

즉, 한 번 생성되면 삭제 불가

내부 구조 (Union File System)

도커는 UnionFS (예: OverlayFS, AUFS) 라는 파일시스템을 사용해서 이 여러 개의 레이어를 하나의 통합된 파일시스템처럼 보여줌

[ 읽기 전용 레이어들 (이미지 레이어) ]
 ├── Base layer (ubuntu)
 ├── apt-get install layer
 ├── COPY index.html layer
[ 쓰기 가능한 레이어 (container layer) ]
 └── 컨테이너 실행 시 생성되는 overlay

즉, 컨테이너가 실행될 때는
이미지의 여러 “읽기 전용 레이어” 위에
“쓰기 가능한 새로운 레이어(컨테이너 레이어)”가 하나 더 생긴다

구분설명특징
이미지 레이어Dockerfile 빌드 과정에서 생긴 읽기 전용 파일시스템변경 불가, 여러 컨테이너가 공유
컨테이너 레이어컨테이너 실행 시 생기는 쓰기 가능한 최상위 레이어컨테이너 종료 시 삭제 가능
개념설명
레이어파일시스템 변경(diff)을 저장한 단위
이미지여러 개의 읽기 전용 레이어들의 집합
컨테이너이미지 레이어 + 쓰기 가능한 레이어
파일시스템OverlayFS로 여러 레이어를 하나로 합쳐서 보여줌
캐싱 효과빌드 시 이전 단계가 동일하면 해당 레이어 재사용

도커 레이어 구성 나쁜 예

RUN apt-get update
RUN apt-get install -y a
RUN apt-get install -y b
RUN apt-get install -y c
RUN apt-get install -y d
RUN apt-get install -y e

6개 레이어

도커 레이어 구성 좋은 예

RUN apt-get update && apt get install -y a b c d e

1개 레이어

실습

mkdir layer
cd layer
echo "file1" > file1.txt
echo "file1" > file2.txt
echo "file1" > file3.txt

vi Dockerfile.bad

FROM ubuntu

문제1: 각 RUN마다 레이어 생성

RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y wget
RUN apt-get install -y vim
RUN apt-get install -y git

문제2: 파일 개별 복사

COPY file1.txt /app/
COPY file2.txt /app/
COPY file3.txt /app/

RUN apt-get install -y python3
RUN apt-cache policy python3 > /tmp/package-info.txt
RUN cat /tmp/package-info.txt
RUN rm /tmp/package-info.txt

CMD ["/bin/bash"]


docker build -f Dockerfile.bad -t test:bad .
docker images test:bad

vi Dockerfile.good

FROM ubuntu

RUN apt-get update && apt-get install -y \
curl \
wget \
vim \
git \
python3 \
&& rm -rf /var/lib/apt/lists/*

COPY file*.txt /app/

RUN apt-cache policy python3 > /tmp/package-info.txt \
&& cat /tmp/package-info.txt \
&& rm /tmp/package-info.txt

CMD ["/bin/bash"]


docker build -f Dockerfile.good -t test:good .

docker images test:good
docker history test:good
docker run -it --name test-bad test:bad bash

ls -lh /var/lib/apt/lists/
du -sh /var/lib/apt/lists/

Multi-stage Build

: 하나의 Dockerfile 안에서 여러 개의 FROM 단계를 사용하여 빌드용 환경과 실행용 환경을 분리하는 기법

  • 1단계(Stage 1): 코드를 빌드/컴파일하기 위한 무거운 이미지
  • 2단계(Stage 2): 실행에 필요한 결과물만 복사해서 만든 가벼운 이미지

⇒ 이렇게 “중간 빌드 환경”을 버리고 최종 실행용 이미지만 남기는 것이 핵심

$ cd ~
$ mkdir python-multistage
$ cd python-multistage/

싱글 스테이지 실습

**$ vi app.py**
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return '<h1>Multi-Stage Build</h1>'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

설치가 필요한 패키지 목록 작성

# 설치가 필요한 패키지 목록
$ vi requirements.txt
# 운영 환경
Flask==3.1.0

# 개발 전용 도구
pytest==8.3.3
black==24.10.0
flake8==7.1.1
mypy=1.13.0
ipython==8.29.0
**$ vi requirements-prod.txt**
Flask==3.1.0

빌드하는 데에 필요한 모든 파일 준비 완료

**$ ls**
app.py  requirements-prod.txt  requirements.txt

도커파일 작성

**$ vi Dockerfile.single**
# 일반적인 방법

FROM python:3.11
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 5000
CMD ["python", "app.py"]

빌드

$ docker build -f Dockerfile.single -t pyapp:single .

빌드된 이미지 확인

**$ docker images pyapp:single**
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
pyapp        single    d3128d664040   20 minutes ago   1.21GB

1.2GB → 크기가 큼

**$ docker history pyapp:single**
IMAGE          CREATED          CREATED BY                                      SIZE      COMMENT
d3128d664040   21 minutes ago   CMD ["python" "app.py"]                         0B        buildkit.dockerfile.v0
<missing>      21 minutes ago   EXPOSE [5000/tcp]                               0B        buildkit.dockerfile.v0
<missing>      21 minutes ago   COPY app.py . # buildkit                        185B      buildkit.dockerfile.v0
<missing>      21 minutes ago   RUN /bin/sh -c pip install --no-cache-dir -r…   103MB     buildkit.dockerfile.v0
<missing>      21 minutes ago   COPY requirements.txt . # buildkit              126B      buildkit.dockerfile.v0
<missing>      21 minutes ago   WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      13 days ago      CMD ["python3"]                                 0B        buildkit.dockerfile.v0
<missing>      13 days ago      RUN /bin/sh -c set -eux;  for src in idle3 p…   36B       buildkit.dockerfile.v0
<missing>      13 days ago      RUN /bin/sh -c set -eux;   wget -O python.ta…   64MB      buildkit.dockerfile.v0
<missing>      13 days ago      ENV PYTHON_SHA256=8d3ed8ec5c88c1c95f5e558612…   0B        buildkit.dockerfile.v0
<missing>      13 days ago      ENV PYTHON_VERSION=3.11.14                      0B        buildkit.dockerfile.v0
<missing>      13 days ago      ENV GPG_KEY=A035C8C19219BA821ECEA86B64E628F8…   0B        buildkit.dockerfile.v0
<missing>      13 days ago      RUN /bin/sh -c set -eux;  apt-get update;  a…   17.9MB    buildkit.dockerfile.v0
<missing>      13 days ago      ENV LANG=C.UTF-8                                0B        buildkit.dockerfile.v0
<missing>      13 days ago      ENV PATH=/usr/local/bin:/usr/local/sbin:/usr…   0B        buildkit.dockerfile.v0
<missing>      21 months ago    RUN /bin/sh -c set -ex;  apt-get update;  ap…   656MB     buildkit.dockerfile.v0
<missing>      21 months ago    RUN /bin/sh -c set -eux;  apt-get update;  a…   185MB     buildkit.dockerfile.v0
<missing>      21 months ago    RUN /bin/sh -c set -eux;  apt-get update;  a…   60.2MB    buildkit.dockerfile.v0
<missing>      21 months ago    # debian.sh --arch 'amd64' out/ 'trixie' '@1…   120MB     debuerreotype 0.16

설치된 패키지 목록 확인

$ docker exec -it py-single bash

root@b637dc90d20f:/app# pip list
Package           Version
----------------- -------
asttokens         3.0.0
black             24.10.0
blinker           1.9.0
click             8.3.0
decorator         5.2.1
executing         2.2.1
flake8            7.1.1
Flask             3.1.0
iniconfig         2.3.0
ipython           8.29.0
itsdangerous      2.2.0
jedi              0.19.2
Jinja2            3.1.6
MarkupSafe        3.0.3
matplotlib-inline 0.1.7
mccabe            0.7.0
mypy              1.13.0
mypy_extensions   1.1.0
packaging         25.0
parso             0.8.5
pathspec          0.12.1
pexpect           4.9.0
pip               24.0
platformdirs      4.5.0
pluggy            1.6.0
prompt_toolkit    3.0.52
ptyprocess        0.7.0
pure_eval         0.2.3
pycodestyle       2.12.1
pyflakes          3.2.0
Pygments          2.19.2
pytest            8.3.3
setuptools        79.0.1
stack-data        0.6.3
traitlets         5.14.3
typing_extensions 4.15.0
wcwidth           0.2.14
Werkzeug          3.1.3
wheel             0.45.1

root@b637dc90d20f:/app# which gcc
/usr/bin/gcc

root@b637dc90d20f:/app# exit

멀티 스테이지 실습

도커파일 작성

**$ vi Dockerfile.multi**
# Stage 1
FROM python:3.11 AS builder
WORKDIR /app

COPY requirements-prod.txt .
RUN pip install --user --no-cache-dir -r requirements-prod.txt

# Stage 2
FROM python:3.11-slim
WORKDIR /app

COPY --from=builder /root/.local /root/.local .
COPY app.py .

ENV PATH=/root/.local/bin:$PATH

EXPOSE 5000
CMD ["python", "app.py"]

“빌드 환경(Stage 1)”과 “실행 환경(Stage 2)”을 분리

최종 이미지를 작고, 깨끗하고, 빠르게 만드는 구조

Stage 1 : Builder

  • python:3.11 : 컴파일 도구·라이브러리 등이 포함된 풀 버전 Python 이미지.
  • WORKDIR /app : 이후 모든 명령의 작업 디렉터리.
  • COPY requirements-prod.txt . : 의존성 목록 복사.
  • RUN pip install ... : 필요한 패키지를 /root/.local/ 아래에 설치.
  • -no-cache-dir : pip 캐시 저장 방지 → 이미지 크기 축소. ⇒ 결과 : Stage 1 에 /root/.local/ 경로가 만들어지고, 거기에 패키지들이 설치됨.

Stage 2 : Runtime

  • python:3.11-slim : 실행만 가능한 경량 이미지 (불필요한 빌드툴 없음).
  • COPY --from=builder /root/.local /root/.local . → Stage 1 에서 설치한 패키지들을 가져옴. (결국 Stage 2 에는 Python 표준 + 의존 패키지 + app.py 만 남음)
  • COPY app.py . : 애플리케이션 코드 복사.
  • ENV PATH=/root/.local/bin:$PATH : pip가 설치한 실행 파일을 PATH에 추가.
  • EXPOSE 5000 : Flask 웹 앱 포트 노출.
  • CMD ["python", "app.py"] : 앱 실행 명령

빌드

**$ docker build -f Dockerfile.multi -t pyapp:multi .

$ docker images pyapp:multi**
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
pyapp        multi     a93c345f7a60   39 seconds ago   129MB

컨테이너 생성

$ docker run -d -p 5001:5000 --name py-mulit pyapp:multi

오류남

$ docker logs py-mulit
Traceback (most recent call last):
  File "/app/app.py", line 1, in <module>
    from flask import Flask
ModuleNotFoundError: No module named 'flask'

도커 파일 모듈 탐색 경로 root 에서 usr 로 바꾸기(--user 사용x)

# Stage 1
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements-prod.txt .
RUN pip install --no-cache-dir -r requirements-prod.txt

# Stage 2
FROM python:3.11-slim
WORKDIR /app
COPY --from=builder /usr/local /usr/local
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]

정상 작동

**$ curl localhost:5001**
<h1>Multi-Stage Build</h1>

설치된 패키지 목록 확인

**$ docker exec -it py-multi bash**

**root@7d0fc1ba5291:/app# pip list**
Package      Version
------------ -------
blinker      1.9.0
click        8.3.0
Flask        3.1.0
itsdangerous 2.2.0
Jinja2       3.1.6
MarkupSafe   3.0.3
pip          24.0
setuptools   79.0.1
Werkzeug     3.1.3
wheel        0.45.1

**root@7d0fc1ba5291:/app# which gcc

root@7d0fc1ba5291:/app# exit**
exit

싱글과 비교하면 확연히 적은 패키지 개수, gcc 또한 없음

⇒ 운영 환경이므로 개발 환경에서 필요한 패키지들이 필요없음

profile
새싹 개발자

0개의 댓글