1. Intro
Growth Log

방통대 과정을 시작하면서, GROWTH LOG라는 내부 소모임에 가입하였다. 아무도 뭐라하지는 않지만 비전공자라는 딱지가 내심 혼자 신경 쓰여 25년 상반기에 한국방통대(Korea National Open University, KNOU) 3학년으로 편입했고, 다른 어떤사람들이 있을까 궁금해서 교류하기위해 학과 소모임을 찾던 중 GROWTH LOG 라는곳이 단순 졸업이 아닌, 그 이후를 위한 네트워킹까지 고려하고 운영되는것같아 참여하게되었다.

이곳에서도 Container, Docker, Kubernetes에 대한 수요가 있었고, 스터디가 생겨서 나름 뭔가 얻어가는게 있지않을까 하고 참가하였다. 과거 몇번 참가했던 적 있던 Cloudnet@팀의 Gasida 님이 진행하시던 스터디에 참가하셨던 다른 멤버분이 리딩하시는 모임이고, 초심자 버전으로 조금 더 쉽게 진행될 예정이다. 시작 시점에서의 참가 목적은 다음과 같다.
- 기존 스터디 내용 리마인드
- 한번 머릿속에 집어넣었던 내용이지만 시간이 지나면 어느 정도 잊기 마련이다. 다시 한번 커맨드 쳐보면서 복기하기
- 참가자가 다름 > 다른분들은 뭘 궁금해하시려나
- 기존 스터디는 현업의 인프라 담당자(SRE/Devops/Network엔지니어) 대상
- 이 스터디는 학생 또는 일반 웹/앱 개발자 대상
- 근무하다보면 아무래도 인프라담당자들보다는 개발자들의 인구가 훨씬 많다는게 보이긴하다. 다른분들은 어떤 부분에 관심을 가지고 있을까, 어떤 부분을 궁금해 하실까, 어떤 부분을 어려워하실까
실습환경준비(AWS, t3.small Ubuntu 1대)
사전 준비
: AWS 계정, SSH 키 페어
구성
: VPC 1개(퍼블릭 서브넷 2개), EC 인스턴스 1대(Ubuntu 22.04 LTS, t3.small)
- CloudFormation 스택 실행 시 파라미터를 기입하면, 해당 정보가 반영되어 배포됩니다.
- CloudFormation 에 EC2의 UserData 부분(Script 실행)으로 실습 환경에 필요한 기본 설정들이 자동으로 진행됩니다.
- 내 PC 공인 IP 확인 – myip.com
- CloudFormation 스택 CLI 배포 ← 실행 PC에 aws cli 설치되어 있고, aws configure 자격증명 설정 상태.
# YAML 파일 다운로드
curl -O https://kimalarm-etc.s3.ap-northeast-2.amazonaws.com/knou/knou-1w.yaml
# CloudFormation 스택 배포
# aws cloudformation deploy --template-file knou-1w.yaml --stack-name docker-stack --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 --region ap-northeast-2
예시) aws cloudformation deploy --template-file knou-1w.yaml --stack-name knou-stack --parameter-overrides KeyName=knou-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
## Tip. 인스턴스 타입 변경 : MyInstanceType=t2.micro
예시) aws cloudformation deploy --template-file knou-1w.yaml --stack-name knou-stack --parameter-overrides MyInstanceType=t3.small KeyName=knou-key SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name knou-stack --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2
# [모니터링] CloudFormation 스택 상태 : 생성 완료 확인
while true; do
date
AWS_PAGER="" aws cloudformation list-stacks \
--region ap-northeast-2 \
--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
# EC2 SSH 접속
ssh -i ~/.ssh/knou-key.pem ubuntu@$(aws cloudformation describe-stacks --stack-name knou-stack --query 'Stacks[*].Outputs[0].OutputValue' --output text --region ap-northeast-2)
- 또는 CloudFormation 콘솔에서, S3 URL을 사용하여 수동생성


2. Docker
2.1. Overview
- 요약
- 컨테이너는 애플리케이션(프로세스) 동작에 필요한 파일들만 패키징된 이미지를 실행하여 동작
- 컨테이너는 컨테이너 환경이 조성된곳 어디에서나(온프레미스/클라우드 환경)에서도 실행 가능
최근(250403) AWSKRUG #platform-engineering 그룹의 개발 생산성 향상을 위한 두산 에너빌리티의 (Gen AI 기반) 플랫폼 엔지니어링 모임에 갔다왔었다. 25년 AWS Summit 발표 예정이라 자료가 공유되지 않았지만, 해당 모임에서 소개된 USB-OPS라고 불리는 기존 사용 환경(폐쇄망, 버전 상이함, 등)이 컨테이너 장점을 살려서 전환하기에 딱이라는 생각이 들었었음
2.2. Docker란
- 도커(Docker)는 거상실행 환경을 제공해주는 오픈소스 플랫폼
- 도커에서는 이 가상실행 환경을
컨테이너(Container)
라고 지칭- 좀 더 구체적인 용어는
컨테이너화된 프로세스(Containerized Process)
- 도커 플랫폼이 설치된 곳이라면 컨테이너로 묶인 애플리케이션을 어디서든 실행할 수 있는 장점
- 좀 더 구체적인 용어는

2.2.1. 컨테이너와 가상머신

🖥️ 가상머신 (Virtual Machine)
- 하이퍼바이저(Hypervisor) 위에 동작
- 하드웨어를 에뮬레이션함 (CPU, Memory, Disk 등 가상 자원 제공)
- 각 VM은 별도의 Guest OS 포함
- 무겁고 부팅 시간 길며 리소스 소모 큼
- 예: Ubuntu 위에 CentOS VM 올릴 수도 있음
- 프로세스는 Guest OS 위에서 실행
- 커널은 각 Guest OS마다 독립적으로 존재
- 격리 수준 높음
- 보안성 및 안정성 우수 (Guest OS끼리 커널 공유 안 함)
- 복잡한 시스템 환경 재현에 유리
- 다양한 운영체제 테스트, 윈도우/리눅스 혼합 등
📦 도커 컨테이너 (Docker Container)
- 호스트 OS의 커널을 직접 공유
- 커널은 하나, 프로세스는 namespace/cgroup 등으로 격리
- Guest OS 없음
- Base image에는 최소한의 사용자 공간만 포함 (Alpine, Ubuntu 등)
- 프로세스는 곧 컨테이너
- 빠른 시작 속도, 가벼운 메모리 사용량
- 파일 시스템, 네트워크, PID 등을 namespace로 격리
- 보안성은 상대적으로 낮음
- 같은 커널을 사용하기 때문에 커널 취약점 영향 공유
- CI/CD, Microservice, 경량 배포에 유리
🧾 요약 비교
항목 | 가상머신 (VM) | 도커 컨테이너 (Container) |
---|---|---|
운영체제 | 각 VM마다 Guest OS 있음 | 호스트 OS 커널 공유 |
커널 공유 | ❌ (독립된 커널) | ✅ (공통 커널 사용) |
부팅 속도 | 느림 | 빠름 |
리소스 사용량 | 무거움 | 가벼움 |
보안 및 격리 | 매우 강함 | 상대적으로 약함 (추가 보안 필요) |
사용 목적 | 완전한 시스템 환경 분리 필요 시 | 빠른 배포, 경량화 서비스 운영 시 |
2.2.2. Docker Architecture

🧱 Docker 아키텍처 구성요소
🔹 1. Client (도커 클라이언트)
- 사용자가 입력하는 CLI 인터페이스 (
docker run
,docker build
,docker pull
등) - 명령어 입력 시 Docker daemon에게 요청을 전달함 (REST API 통신)
🔹 2. Docker Daemon (도커 데몬) / = Docker Host
- Docker의 핵심 엔진 (
dockerd
) - 클라이언트 요청을 받아 이미지 빌드, 컨테이너 실행, 레지스트리와 통신 등을 담당
- 로컬에 이미지 저장소 및 컨테이너 실행 상태 관리
🔹 3. Images (이미지 저장소)
- 컨테이너 생성의 청사진 역할
- Python, Redis 등 다양한 base image가 존재
- 도커는 이미지를 기반으로 컨테이너 생성
🔹 4. Containers (컨테이너 런타임)
- 이미지 기반으로 실제 실행되는 인스턴스
- 컨테이너는 일종의 경량 프로세스이며, 실행 시 독립된 환경으로 동작
🔹 5. Registry (도커 이미지 저장소)
- 도커 이미지를 저장하고 공유하는 중앙 서버
- Public: Docker Hub, Private: self-hosted registry
docker pull
은 registry에서 이미지 다운로드docker push
는 만든 이미지를 registry에 업로드
🔹 6. Extensions & Plugins
- Docker의 기능을 확장
- Extensions: 관리, 보안, 개발 도구 (예: JFrog, Portainer 등)
- Plugins: 스토리지/네트워크 기능 추가 (예: vSphere, Grafana 등)
2.3. 도커 설치
2.3.1. 사전 지식) Linux Process, /proc

- 리눅스에서 프로세스는 실행 중인 프로그램의 인스턴스를 의미.
- 각 프로세스는 고유한 PID(Process ID)를 가지며, 커널에 의해 관리됨.
- 프로세스는 CPU, 메모리, 파일 디스크립터 등의 자원을 할당받아 동작.
/proc
디렉토리는 리눅스 커널이 제공하는 가상 파일 시스템, 각 프로세스의 정보 확인 가능./proc/<PID>/
경로를 통해 특정 프로세스의 상태, 메모리 사용량, 파일 열기 상태 등을 확인 가능
2.3.2. 도커 설치 및 확인
- Docker 홈페이지의 Install Docker Engine on Ubuntu에서 확인가능
- apt repo, .deb, Install scripts 등 방법제공
- 편한건 역시 자동화 스크립트
Docker Install
# [터미널1] 관리자 전환
sudo su -
whoami
# 도커 설치
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
# 도커 루트 디렉터리 확인 : Docker Root Dir(/var/lib/docker)
tree -L 3 /var/lib/docker
# Ubuntu 사용자를 docker 그룹에 추가 (docker socket 접근 권한)
sudo usermod -aG docker Ubuntu
# 위 명령어 입력 후 SSH 재접속
exit
결과 확인
- docker version 및 status

2.3.3. 컨테이너 실행 및 확인
도커 기본 명령어
# 현재 컨테이너 프로세스 조회
docker ps
docker ps -a
# 이미지 다운로드
docker pull hello-world:latest
# latest: Pulling from library/hello-world
# e6590344b1a5: Pull complete
# Digest: sha256:c41088499908a59aae84b0a49c70e86f4731e588a737f1637e73c8c09d995654
# Status: Downloaded newer image for hello-world:latest
# docker.io/library/hello-world:latest
# 로컬 이미지 조회
docker images
# 도커 실행
docker run --help
docker run --name first-docker hello-world:latest
docker run --name nginx-docker -p 3000:80 -d nginx:latest
# 네트워크 상태 조회
netstat -ntlp
# 컨테이너 프로세스 재조회
docker ps --help
docker ps
docker ps -a
# 컨테이너 로그 조회
docker logs --help
docker logs first-docker
docker logs -f nginx-docker
# 컨테이너 내부에서 sh 실행
docker exec --help
# [터미널 2]
docker exec -it nginx-docker /bin/bash
curl http://localhost
#
exit
curl http://localhost:3000
curl https://ipinfo.io/ip
# curl $(curl -s https://ipinfo.io/ip):3000
# > EC2에서 실행하면 EC2의 PubIP
# > Mac,Win등 로컬에서 실행하면 Local PubIP
# IP 가 다른 이유는?? -> 컨테이너 네트워크에서 다룰 내용
# 127.0.0.1 - - [18/Apr/2025:15:18:47 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.88.1" "-"
# 172.17.0.1 - - [18/Apr/2025:15:19:02 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.81.0" "-"
# 3.36.103.85 - - [18/Apr/2025:15:21:21 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.81.0" "-"
# 116.43.168.146 - - [18/Apr/2025:15:22:01 +0000] "GET / HTTP/1.1" 200 615 "-" "Mozilla/5.0 (Windows NT; Windows NT 10.0; ko-KR) WindowsPowerShell/5.1.19041.5737" "-"
# 컨테이너 정보 조회
docker inspect nginx-docker
... 생략 ...
"Networks": {
"bridge": {
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
}
}
... 생략 ...
# 컨테이너 네트워크 조회
docker network ls
docker network inspect bridge
docker network inspect bridge | grep 'Containers' -A8
## 172.17.0.1 은 어디서 온걸까?
ip addr
ip addr show dev docker0
# 컨테이너 중지
docker stop nginx-docker
docker stop $(docker ps -aq)
# 컨테이너 제거
docker rm nginx-docker
docker rm $(docker ps -aq)
# 컨테이너 이미지 제거
docker rmi nginx:latest hello-world:latest
결과 확인
- nginx 컨테이너에 http 요청시 방식에 따라 서로 다른 소스IP가 찍힘
- 컨테이너 내부) curl http://localhost > 127.0.0.1
- EC2) curl http://localhost:3000 > 172.17.0.1
- EC2) curl $(curl -s https://ipinfo.io/ip):3000 > 3.36.103.85
- Win) curl $(curl -s https://ipinfo.io/ip):3000 > 116.43.168.146
- nginx 컨테이너의 네트워크를 조회하면 Gateway와 컨테이너의 IP조회 가능
- GW > 172.17.0.1
- nginx > 172.17.0.2

자주 사용하는 docker run 명령어 옵션
docker run --help
# 필수로 알아야 하는 옵션
-d : [Detach] 백그라운드에서 컨테이너 프로세스 실행 (nohup &)
-i : [Interactive] 표준입력 연결 (입력) (-it 는 세트)
-t : [tty] 가상 터미널 생성 - 상호작용 (출력) (-it 는 세트)
--name : 컨테이너 이름 지정
-p : [Publish] 호스트 포트와 컨테이너 포트 매핑
--restart : 재시작 정책 [always 항상, on-failure 에러 코드 0이 아닌 경우, unless-stopped 수동으로 중지하지 않는 한]
-v : [volume] 호스트 볼륨 마운트
# 알아두면 좋은 옵션
-u : [user] 컨테이너 내부 실행 유저
--rm : 컨테이너 종료 시 자동 삭제, 임시 사용 [restart 와는 동시 사용 불가]
## 참고
# --restart 와 --rm 은 같이 사용할 수 없다.
# -d 와 -it 는 함께 사용할 수 없다. (docker exec -it 활용)
# 예시
mkdir ~/volume-mount && cd ~/volume-mount && touch hello.txt
docker run \
--name nginx-docker \
-d \
-p 3000:80 \
-v $(pwd):/tmp-dir \
--restart always \
nginx:latest
docker exec -it nginx-docker /bin/bash
결과 확인
- docker
- run 명령어로 컨테이너 실행
- -d 옵션으로 백그라운드 보내기
- exec 명령어로 컨테이너 내부에서 명령어 실행
- -it 옵션으로 포그라운드 가져오고 bash 쉘 실행
- -v 옵션을 통해 지정한 볼륨/파일이 컨테이너 내부와 동기화
- ~/volume-mount/hello.txt = /tmp-dir/hello.txt
- run 명령어로 컨테이너 실행

3. Docker Network
3.1. Docker Network Model
- 기본 네트워크 모드 : Bridge, Host, None + 추가 네트워크 플러그인 : macvlan, ipvlan, overlay
- Bridge (기본값)
- 컨테이너는 가상 브릿지 네트워크 (
docker0
)에 연결됨 - 컨테이너끼리 통신 가능, 외부 접근은 포트 포워딩 필요 (
-p
) - NAT 기반 IP 할당
- 컨테이너는 가상 브릿지 네트워크 (
- Host
- 컨테이너가 호스트의 네트워크 네임스페이스를 그대로 사용
- 포트 포워딩 없이 호스트 IP/포트 직접 사용 가능
- 컨테이너의 호스트 이름도 호스트 머신의 이름과 동일
- 격리 수준 낮지만 성능은 높음
- None
- 네트워크 연결 없음 (인터페이스 없음)
- 완전 격리된 네트워크 환경
- 컨테이너 내부에 lo (loopback?)인터페이스만 존재
- Bridge (기본값)


3.2. Bridge mode
- Bridge 모드 기본 정보
- 도커에서 기본적으로 쓸 수 있는 네트워크
- 컨테이너 기본 생성 시 자동으로 docker0 브리지를 사용
- 기본 172.17.0.0/16 대역을 컨테이너가 사용, 대역 변경 설정 가능
- 도커 컨테이너 간 상호 통신 허용, 호스트 컨테이너 간 통신 허용
컨테이너 내부 통신 테스트
# 터미널1 (kn) : kn 이름의 busybox 컨테이너 생성
docker run -it --name=kn --rm busybox
ip addr
# 터미널2 (ou) : ou 이름의 busybox 컨테이너 생성
docker run -it --name=ou --rm busybox
ip addr
# 터미널3 (호스트)
docker ps
# 터미널3 (호스트)
sudo tcpdump -i docker0 -n
# 터미널1 (kn)
# Ping (kn -> ou)
ping -c 4 172.17.0.3
# 터미널2 (ou)
# Ping (ou -> kn)
ping -c 4 172.17.0.2
컨테이너 외부 통신 테스트
# 터미널3 (호스트)
sudo tcpdump -i any icmp
# 터미널1 (kn)
# Ping (kn -> google.com)
ping -c 1 8.8.8.8
# 터미널2 (ou)
# Ping (ou -> kn)
ping -c 1 8.8.8.8
결과 확인
- 컨테이너 내부 통신 테스트
- sudo tcpdump -i docker0 -n
- 이름이 docker0인 인터페이스(Docker bridge)의 실시간 트래픽 캡쳐
- 두 서버(172.17.0.2,4)간 ping(ICMP) 패킷 확인

- 컨테이너 외부 통신 테스트
- sudo tcpdump -i any icmp
- 모든 네트워크 인터페이스에서 ICMP(ping) 요청 확인
- 두 서버가 퍼블릭망의 8.8.8.8(google DNS Server) 요청시 패킷 확인

3.3 Host 모드
- Host 모드 기본 정보
- 호스트 네트워크 환경을 컨테이너에서 그대로 사용
Host 모드 통신 테스트
# 컨테이너 실행
docker run --rm -d --network host --name my_nginx nginx
# HostConfig.NetworkMode "host" , Config.ExposedPorts "80/tcp" , NetworkSettings.Networks "host" 확인
docker inspect my_nginx
docker inspect my_nginx | grep NetworkMode
docker inspect my_nginx | grep -A 1 Networks
docker inspect my_nginx | grep -A 2 ExposedPorts
# 네트워크 상태 확인
netstat -ntlp
# curl 접속 확인
curl -s localhost | grep -o '<title>.*</title>'
# 추가 실행 시도
docker run -d --network host --name my_nginx_2 nginx
# 확인
docker ps -a
# 삭제
docker container stop my_nginx
결과 확인
- my_nginx 컨테이너의 네트워크 정보 확인 > host 모드, tcp 80 port 사용
- localhost의 기본 80 포트 요청시 성공적으로 응답

- host와 port를 공유하기에, 같은 포트에 서로 다른 프로세스 동작 불가
- 같은 80포트를 사용하는 my_nginx_2는 포트 사용중이라 생성 실패

3.4. None 모드
- None 모드 기본 정보
- 컨테이너에 네트워크 설정을 부여하지 않는 모드
None 모드 통신 테스트
# None 네트워크로 Nginx 실행
docker run --rm -d --network none --name my_nginx nginx
# Docker Inspect 로 정보 확인
docker inspect my_nginx | grep NetworkMode
# 네트워크 정보 조회
netstat -ntlp
# 컨테이너 내부 환경 확인
docker exec -it my_nginx /bin/bash
ip addr
ping 8.8.8.8
curl localhost
결과 확인
- ip, ping 없는데?!?! 영상다시봐야겠다
- 아마 loopback interface(lo) 만있고 퍼블릭 못나가고 로컬은 되겠지
4. Docker Image build
4.1. Dockerfile

Dockerfile
– Overview, Reference- Docker Image 생성을 위한 일련의 설계도
- 어떤 OS, 파일 복사 내용, 어떤 패키지 설치, 어떤 명령어 수행을 순서대로 적어둔 스크립트 파일
- docker build 명령어 수행 시 옵션으로 주입하여 사용
- Dockerfile 요소
FROM
: 베이스 이미지 지정 (필수)WORKDIR
: 작업 디렉토리 지정 (cd 명령어)COPY
: 호스트에서 컨테이너로 파일 및 디렉토리 복사RUN
: 쉘 명령어 수행ENV
: 환경 변수 설정ARG
: 빌드 시 인자 설정 (빌드 후 제거)CMD
: 컨테이너 시작 시 실행할 기본 명령어ENTRYPOINT
: CMD 와 비슷하나, 고정 명령
Dockerfile test
# 실습을 위해 기존 이미지 제거
docker stop $(docker ps -aq)
docker rm $(docker ps -aq)
docker rmi $(docker images -aq)
# 테스트 디렉토리 생성
mkdir ~/python && cd ~/python
cat << EOF >> app.py
from http.server import BaseHTTPRequestHandler, HTTPServer
class Handler(BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header('Content-type','text/plain')
self.end_headers()
self.wfile.write(b"Hello, Docker!")
if __name__ == '__main__':
server = HTTPServer(('0.0.0.0', 8000), Handler)
print("Server running on port 8000...")
server.serve_forever()
EOF
cat << EOF >> hello.txt
Hello World
EOF
cat << EOF >> dockerfile
# 베이스 이미지
FROM python:3.10-slim
# 컨테이너 안에 디렉토리 만들고 이동
WORKDIR /app
# 전체 디렉토리 파일 복사
COPY . .
# 컨테이너가 실행할 기본 명령
CMD ["python", "app.py"]
EOF
# Docker build 명령어 수행 (파일명이 dockerfile 일 경우 -f 생략 가능
docker build -t my-first-python -f dockerfile .
#
docker images
# 터미널 1
docker run --rm -p 8000:8000 my-first-python
# 터미널 2
netstat -ntlp
curl http://localhost:8000; echo
결과 확인
- 8000포트로 요청하면 텍스트 찍는 파이썬 http 서버 업
- 호스트의 8000포트 개방 확인 (-p 8000:)

4.2. .dockerignore
.dockerignore
파일.gitignore
처럼 docker build 시 Container 내부에 복사하지 않도록 지정하는 파일- dockerfile 과 같은 경로에 위치
- Why??
- 민감 정보 유출 방지
- 불필요한 파일을 제외하여 빌드 속도 상승
.dockerignore 실습
# 터미널1 - 기존 컨테이너 실행
docker run -d -p 8000:8000 --name origin my-first-python
docker exec -it origin /bin/bash
ls -lah
# 터미널2
# .dockerignore 파일 생성
cat << EOF >> .dockerignore
# hello.txt 제외
hello.txt
EOF
#
docker build -t my-second-python -f dockerfile .
#
docker run -d -p 8001:8000 --name modify my-second-python
docker exec -it modify /bin/bash
#
ls -lah
# 실습 후 컨테이너 제거
ㅊdocker stop $(docker ps -aq)
docker rm $(docker ps -aq)
docker rmi $(docker images -aq)
결과 확인
- ~/python dir의 .dockerignore 생성됨에 따라 hello.txt 제외되고 빌드됨

4.3. docker image 빌드 시 고려 사항
4.3.1. 이미지 크기 단축
- Alpine Image – docker hub, Blog
- 컨테이너 이미지가 크면 클수록 빌드 & 배포 & 가동 시간이 길어짐
- 클라우드 환경일 경우 네트워크 트래픽이 증가하여 비용 상승
- 경량화된 이미지인
alpine
,slim
활용 권장
- Python에서 Alpine 이미지를 사용하면 안 되는 이유 – Link
- 요약 : Python 은 내부적으로는 C 를 사용하는데, alpine 은 C 컴파일을 위한 라이브러리가 존재하지 않음 → Slim 이미지 권장
- 다만, 잘 돌아가면 Alpine 을 탈피할 필요 X
Alpine image 실습
mkdir ~/alpine && cd ~/alpine
# 예제 파일 생성
cat << EOF >> hello.py
print("Hello from Docker!")
EOF
# 기본 파이썬 이미지
cat << EOF >> normal-dockerfile
FROM python:3.10
WORKDIR /app
COPY hello.py .
CMD ["python", "hello.py"]
EOF
# Alpine 파이썬 이미지
cat << EOF >> alpine-dockerfile
FROM python:3.10-alpine
WORKDIR /app
COPY hello.py .
CMD ["python", "hello.py"]
EOF
# 이미지 빌드
docker build -t normal -f normal-dockerfile .
# [+] Building 21.5s (8/8) FINISHED
docker build -t alpine -f alpine-dockerfile .
# [+] Building 3.6s (8/8) FINISHED
# 이미지 확인
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
alpine latest c4a15fbdfd7d About a minute ago 51.1MB
normal latest 4e4036df0902 2 minutes ago 1GB
결과 확인
- Debian 12(bookworm) 대비 alpine의 빠른 빌드 속도, 경량 확인

- Multi Stage Build
- 하나의 Dockerfile 내에서 여러 개의
FROM
스테이지를 사용 - 빌드용 환경과 실행용 환경을 분리해 불필요한 파일 제외 가능
- 최종 이미지에는 실행에 필요한 최소한의 파일만 포함 > 이미지 크기 감소
- 하나의 Dockerfile 내에서 여러 개의
- 예시:
- Java
- 1단계: Java 소스 코드 빌드 →
.jar
파일 생성 - 2단계: 경량 JRE 이미지에
.jar
파일만 복사 후 실행
- 1단계: Java 소스 코드 빌드 →
- Node.js
- 1단계:
node:alpine
기반으로npm install
과 빌드 수행 - 2단계:
node:alpine
또는nginx
이미지에dist/
폴더만 복사 후 실행
- 1단계:
- Java
- BuildKit 이란?? – Overview
- Differences between legacy builder and BuildKit – Docs
- BuildKit은 기존 Docker 빌드 시스템을 대체하는 향상된 백엔드 엔진
- Docker Desktop과 Docker Engine 23.0 이상에서는 기본 빌드 도구로 사용
- 빌드 성능을 개선하고 복잡한 빌드 시나리오도 처리할 수 있는 기능들을 제공
- 사용되지 않는 빌드 스테이지를 자동 감지 및 건너뛰기
- 서로 독립적인 스테이지는 병렬로 빌드하여 속도 향상
- 빌드 컨텍스트의 변경된 파일만 점진적으로 전송
- 사용되지 않는 파일은 전송하지 않도록 자동 감지
- 다양한 기능을 가진 Dockerfile 프론트엔드 구현체 지원
- 중간 이미지/컨테이너 등의 API 부작용 최소화
- 빌드 캐시 우선순위 설정을 통해 자동 캐시 정리 전략 적용 가능
Multi Stage Build 실습
mkdir ~/multi && cd ~/multi
cat << EOF >> main.go
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
EOF
# 기존 빌드
cat << EOF >> origin-dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.23
WORKDIR /src
COPY main.go .
RUN go build -o /bin/hello ./main.go
CMD ["/bin/hello"]
EOF
# Multi Stage Build
cat << EOF >> multi-dockerfile
# syntax=docker/dockerfile:1
FROM golang:1.23 AS build
WORKDIR /src
COPY main.go .
RUN go build -o /bin/hello ./main.go
FROM scratch
COPY --from=build /bin/hello /bin/hello
CMD ["/bin/hello"]
EOF
# 이미지 빌드
docker build -t origin -f origin-dockerfile .
docker build -t multi -f multi-dockerfile .
# 컨테이너 실행 확인
docker run origin
docker run multi
# 이미지 확인
docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
multi latest b1288266668c 21 seconds ago 2.13MB
origin latest a75c44b075a8 31 seconds ago 870MB
결과 확인
- 동일한 소스코드에서 만든 이미지지만 불필요 종속성 제거로 용량감소

4.3.2. 멀티 플랫폼 / 아키텍처


- 멀티 플랫폼 빌드 – Overview
- 하나의 빌드 이미지, 태그를 통해 각기 다른 CPU 아키텍처에서 사용할 수 있는 이미지를 생성하는 것
ARM
CPU Architecture 를 가진 서버와 컴퓨터가 인기를 끌면서 사용 (EC2 Graviton, Mac Apple Silicon)linux/amd64
,linux/arm64
,windows/amd64
5. Docker Image Security
- 컨테이너화된 애플리케이션을 맬웨어와 취약점으로부터 보호하는 보안 프로세스
- 빌드, 배포, 런타임 단계 전반에 걸쳐 보안 정책을 정의하고 준수하는 작업 포함
- 격리된 환경에서 실행되지만, 커널을 공유하기 때문에 보안 취약점이 전파될 수 있음
- 주요 구성 요소
- 컨테이너 파이프라인과 애플리케이션 보안 / image, 코드 취약점(CVE), 민감정보(secret) 등
- 컨테이너 배포 환경과 인프라 보안 / 네트워크 보안, 접근제어, OS 및 커널 취약점 등
- 런타임 시 컨테이너화된 워크로드 보안 유지 / 비정상행위 탐지, 과도한 권한 부여 방지, 이상 행위 알림 및 차단
보안 가이드
- 2019년, SK Infosec 클라우드 보안 가이드 (컨테이너 보안)
- 2024년, KISA 클라우드 취약점 점검 가이드 – Link
- 2024년, NHN Cloud 컨테이너 보안클라우드 가이드 – Link
5.1. 컨테이너 User 변경

- non-root, rootless, unprivileged, least privilege, UID != 0, etc …
- 2.2.1. 컨테이너와 가상머신에서 살펴본것처럼 VM과 달리 컨테이너는 호스트와 같은 커널을 공유함
- 컨테이너가 탈취될 경우 공격의 범위가 컨테이너를 넘어 호스트머신, 또는 시스템 전파로의 전파 가능성 존재
root user의 경우
- root 권한을 가진 컨테이너 생성
- 생성 이후 기본 유저와 ID 및 root 권한의 명령어 시도
mkdir ~/non-root && cd ~/non-root
cat << EOF >> hello.sh
#!/bin/sh
echo "Hello from multi-architecture Docker image!"
EOF
chmod +x hello.sh
cat << EOF >> root-dockerfile
FROM ubuntu
COPY hello.sh /hello.sh
CMD ["/hello.sh"]
EOF
# 빌드
docker build -t root -f root-dockerfile .
docker run -it root /bin/bash
#
whoami
id
apt update -y && apt install nginx -y
non-root 유저의 경우
- root 권한을 가진 컨테이너 생성
- 생성 이후 기본 유저와 ID 및 root 권한의 명령어 시도
cat << EOF >> non-root-dockerfile
FROM ubuntu
RUN useradd -m -u 1001 appuser
USER appuser
COPY hello.sh /home/appuser/hello.sh
EOF
# 빌드
docker build -t non-root -f non-root-dockerfile .
docker run -it non-root /bin/bash
#
whoami
id
apt update -y
결과 확인
- root 권한을 가진 컨테이너의 경우 UID0 의 root로 실행 및 root권한 사용 가능

- non-root 권한 USER의 경우 별도 UID 할당 및 Permission denied

5.2. 클라이언트 인증 활성화
Docker 명령어를 아무나 수행할 수 없도록 인증 단계 추가 – OPA
- OPA (Open Policy Agent)
- 정책 기반 접근 제어(Policy-as-Code)를 구현할 수 있도록 도와주는 오픈소스 엔진
- 정책 결정을 애플리케이션 로직과 분리해서 관리 가능
- JSON 기반 입력에 대해 Rego라는 선언형 언어로 정책을 정의하고 실행
- Kubernetes, API Gateway, CI/CD, 마이크로서비스 등 다양한 시스템에 통합 가능
OPA Plugin 활성화
docker plugin ls
# 정책 디렉토리 생성
sudo mkdir -p /etc/docker/policies
sudo touch /etc/docker/policies/authz.rego
# 모든 사용자에게 모두 허용
echo "package docker.authz
allow = true" | sudo tee -a /etc/docker/policies/authz.rego
# Docker Plugin Install - OPA
## 수락 필요
sudo docker plugin install openpolicyagent/opa-docker-authz-v2:0.4 opa-args="-policy-file /opa/policies/authz.rego"
# Docker Daemon 설정
if [ ! -f /etc/docker/daemon.json ]; then
sudo touch /etc/docker/daemon.json
fi
echo "{
\"authorization-plugins\": [\"openpolicyagent/opa-docker-authz-v2:0.4\"]
}" | sudo tee -a /etc/docker/daemon.json
# 설정 확인
cat /etc/docker/daemon.json
# 플러그인 활성화 적용
sudo systemctl restart docker
# 정책 변경 (모두 사용 불가)
sudo vim /etc/docker/policies/authz.rego
allow = false 변경
# Docker 명령어 수행
docker ps
결과 확인
journalctl -u docker.service -f
명령어로 docker daemon log 확인- allow = true > OPA policy decision: true 로그 확인
- allow = false > OPA policy decision: false 로그 확인

