필터:#Docker×

[Docker] Compose 파일 작성법과 주요 옵션 총정리

Docker compose 도커(docker run)의 경우에는 단일 컨테이너를 실행한다. 도커 컴포즈는 여러 종류의 컨테이너를 한번에 띄울 수 있다. ex) 도커로 3tier를 구성한다면, docker run 을 3번 해서 webwasdb 컨테이너를 각각 구성해야 하고 —link도 구성해줘야 한다. 하지만 docker compose의 경우엔 3종류의 컨테이너를 컴포즈 파일 하나에 서 동시에 구성이 가능하다. 컨테이너를 띄우는 목적은 뭘까? 서비스를 제공하고 싶기 때문이다. 컨테이너 안에 앱, 애플리케이션이 들어있다. 도커 컴포즈에서는 이런 한 종류의 컨테이너를 service라고 지칭한다. mkdir /com cd /com vi dockercompose.yml dockercompose.yml 이라는 파일명 = 디폴트값 한 개의 서비스로 구성된 도커 컴포즈 파일 편집 services: 내가 띄울 다양한 종류의 컨테이너들 webserver: 서비스의 이름. 내가 원하는대로 정하면 됨. image: nginx ports: '8787:80' publish 기능 networks: webnet 아직 만들진 않았다. 밑에서 만들예정. 놓여질 네트워크 networks: webnet: webnet이라는 이름의 네트워크 생성. 샘플 코드. docker compose up 잘 동작한다. docker compose down 도커 컴포즈 삭제 docker compose up d docker run 과 비슷하게 d (detach) 백그라운드로 동작할 수 있다. 볼륨 구성 호스트의 현재 디렉토리에 vtest라는 디렉토리를 구성한 후 간단한 인덱스 파일을 생성하여 컨테이너에 볼륨 구성을 통해 웹루트디렉토리에 넣어주고 싶다. mkdir ./vtest 경로 생성. echo composevolumetest vtest/index.html 인덱스 파일 생성. vi dockercompose.yml services: 내가 띄울 다양한 종류의 컨테이너들 webserver: 서비스의 이름. 내가 원하는대로 정하면 됨. image: nginx ports: '8787:80' publish 기능 networks: webnet 아직 만들진 않았다. 밑에서 만들예정. 놓여질 네트워크 volumes: ./vtest:/usr/share/nginx/html 호스트의 ./vtest를 컨테이너의 /usr/share/nginx/html에 마운트 networks: webnet: webnet이라는 이름의 네트워크 생성. docker compose up d 마운트가 잘 되었다. dockercompose.yml 옵션들에 대해 알아보자. networks: 네트워크 정의 및 선택 ports: \p, publish 옵션 volumes: \v, 호스트와 컨테이너 마운트 command: CMD environment: \e, 환경변수 dependson: 의존성 설정(컨테이너 띄울 순서)도 가능. ex) node.js 앱(todo, weather, chat)의 경우 몽고디비가 연동이 안되면 앱 자체가 죽어버린다. node.js 서비스를 정의하면서 몽고디비를 depends\on 으로 걸어줘야 한다. /com의 ubun 이라는 경로에서 위 명령어를 활용하여 ubuntu:latest를 동작시키는 compose 파일을 만들어 보자. 네트워크 : ubun\net mkdir ubun cd ubun/ vi dockercompose.yml services: ubun: image: ubuntu:latest networks: ubunnet command: "sleep infinity" networks: ubunnet: docker compose up d docker compose down environment: ENVTEST=test ubun 서비스에 환경변수 추가. docker compose up d docker exec it ubunubun1 bash 환경변수가 잘 들어있다. 도커 컴포즈에서는 —link 를 굳이 명시하지 않아도 서비스의 이름으로 찾아갈 수 있다. wordpress 구성하기! mysql:8, wordpress:latest를 베이스 이미지로 하여, Dockerfile은 쓰지않고, dockercompose.yml 파일을 구성해보자. publish 포트는 wordpress는 1234로 하고, mysql는 따로 publish 하지 않는다. mkdir wp cd wp vi dockercompose.yml services: wp: image: wordpress:latest ports: '1234:80' environment: WORDPRESSDBHOST=dbdb WORDPRESSDBNAME=wpdb WORDPRESSDBUSER=wpuser WORDPRESSDBPASSWORD=1234 networks: wpnet dependson: dbdb dbdb: image: mysql:8 environment: MYSQLROOTPASSWORD: '1234' MYSQLUSER: 'wpuser' MYSQLPASSWORD: '1234' MYSQLDATABASE: 'wpdb' networks: wpnet networks: wpnet: 성공! 기존에 존재하는 네트워크를 사용하고 싶을 때 docker network create mynet driver=bridge 네트워크 생성. vi dockercompose.yml 172.19.0.0 대역에 생성됐다. 빌드한 이미지로 서비스 구성 mkdir build cd build/ vi Dockerfile FROM httpd:latest 도커 파일 생성. vi dockercompose.yml services: myweb: build: . dockercompose.yml 파일이 있는 경로(.)의 Dockerfile을 빌드 도커 컴포즈 파일 생성. docker compose up d echo buildtest index.html index.html 파일 생성. vi Dockerfile 위에서 생성한 index.html 파일을 넣어보자. FROM httpd:latest WORKDIR /usr/local/apache2/htdocs COPY index.html index.html docker compose up d curl localhost:5959 기본 페이지가 뜨고 dockercompose up d 를 했지만 이미지가 빌드되지 않았다. 이미 빌드를 한 적이 있어서 빌드된 이미지가 존재하면 다시 빌드하지 않는다. docker compose down 컨테이너 종료. docker compose up d build —build 옵션을 명시해야 계속 빌드한다. 다시 curl을 찍어보면, 수정한 Dockerfile 대로 다시 빌드가 되었다. docker compose down 컨테이너 종료. 이번엔 빌드를 하 되, 도커파일을 지정해줘보자. vi dockercompose.yml services: myweb: build: dockercompose.yml 파일이 있는 경로(.)의 Dockerfile을 빌드 context: . 디렉토리 지정 dockerfile: Dockerfile 도커파일의 이름 지정. image: myimg:1 빌드해서 나온 이미지의 이름. 단독으로 쓰일때와는 의미가 좀 다르다. ports: '5959:80' context: 도커가 빌드를 시작할 때 참고할 작업 폴더의 위치. context: . 으로 되어있으면 도커는 현재 폴더에 있는 Dockerfile, 소스코드, package.json 등을 모두 빌드 컨텍스트 라는 임시 공간에 담는다. 위 코드 중 context, build 부분 해석 → Dockerfile 란 이름의 도커파일로 빌드를 하는데, 지정한 디렉토리(현재 디렉토리)의 모든 내용을 빌드해라. docker compose scaling —scaling = 로 서비스를 구성하는 컨테이너의 수를 변경할 수 있다. dockercompose.yml 파일에 포트 범위를 명시해준다. 하나의 접속 지점을 통해 로드밸런싱이 됐으면 좋겠지만 docker compose 에서는 그게 불가능하다. 서비스를 구성하는 컨테이너마다 호스트의 포트를 1:1 로 맵핑시켜야 한다. 왜? 도커 컴포즈에서 scale 옵션을 통해 컨테이너 개수를 늘리는 것은 동일한 서비스를 여러 개 복제하여 실행하는 기능에 불과하다. 이 방식은 단순히 물리적인 숫자를 늘려줄 뿐, 외부에서 들어오는 트래픽을 각 컨테이너의 상태나 부하 정도에 맞춰 지능적으로 분산해주는 전문적인 로드밸런싱 기능을 포함하지 않는다. 따라서 여러 개의 컨테이너를 띄우더라도 특정 컨테이너에만 요청이 몰리거나, 장애가 발생한 컨테이너로 접속이 시도되는 등의 한계가 존재한다. 이를 해결하기 위해서는 Nginx나 HAProxy와 같은 별도의 로드밸런서 서비스를 도커 컴포즈 설정에 추가하여 앞단에 배치해야 한다. 전문 로드밸런서는 들어오는 모든 요청을 단일 지점에서 받아 각 컨테이너로 골고루 전달하고, 컨테이너의 생존 여부를 실시간으로 확인하여 정상적인 곳으로만 트래픽을 보내는 가이드 역할을 수행한다. 결과적으로 효율적인 트래픽 분산과 안정적인 서비스 유지를 위해서는 단순한 개수 확장보다는 로드밸런서를 통한 체계적인 관리가 필수적이다.

May 12, 2026Docker
[Docker] Compose 파일 작성법과 주요 옵션 총정리

ECS vs Docker Swarm 컨테이너 오케스트레이션 비교 프로젝트

ECS vs Docker Swarm 컨테이너 오케스트레이션 비교 프로젝트 "성능 비교에서 운영 철학 비교로 — 실패가 아닌 발견의 과정" 프로젝트 개요 항목 내용 프로젝트 유형 인프라 비교 실험 기간 2026년 4월 목표 동일 EC2 환경에서 Docker Swarm과 Amazon ECS의 성능·운영 특성 비교 핵심 기술 Docker Swarm, Amazon ECS(EC2 Launch Type), AWS ALB, Prometheus, Grafana, k6 인프라 EC2 t3.small × 2대, ALB, VPC(퍼블릭 서브넷 단일 구성) 초기 목표 CPU 부하 상황에서의 오토스케일링 반응 속도 비교 컨테이너 강제 종료 후 복구 시간(Recovery Time) 측정 동일 애플리케이션 · 동일 부하 시나리오 기반의 공정한 비교 시스템 구성 공통 환경 EC2 t3.small × 2대 (각 2GB RAM) ├── 애플리케이션 컨테이너 (Spring Boot /env, /stress, /health) ├── cAdvisor (컨테이너 메트릭 수집) └── nodeexporter (시스템 메트릭 수집) 모니터링: Prometheus + Grafana 부하 생성: k6 (rampingarrivalrate 방식) 로그 수집: Python autoscaletest.py (2초 간격 컨테이너 수 추적) Docker Swarm 구성 Manager Node (클러스터 제어 + ingress 담당) Worker Node 1 ─┐ Worker Node 2 ─┘ → 앱 컨테이너 실행 (2 replicas) 네트워크: bridge / overlay 스케줄링: 로컬 Besteffort 방식 Amazon ECS 구성 ECS Cluster ├── EC2 인스턴스 1 (ECS Agent + 앱 Task + cAdvisor) └── EC2 인스턴스 2 (ECS Agent + 앱 Task + cAdvisor) Task 스펙: 0.25 vCPU / 0.5GB → 0.5 vCPU / 0.5GB (진행과정에서 변경) 네트워크: bridge → awsvpc 전환 ALB + Target Group(IP 타입) + Health Check(/health) 오토스케일링: CloudWatch CPU 70% 초과 시 Task 증가 비교 공정성 가설 및 검증 가설 — "Swarm 3대 vs ECS 2대가 공정한 비교다" 두 플랫폼을 같은 인스턴스 수(예: 둘 다 2대)로 맞추는 것이 얼핏 공정해 보이지만, 실제 애플리케이션을 처리하는 데이터 플레인 자원을 기준으로 보면 다르다. Docker Swarm은 Manager 노드가 클러스터 제어(오케스트레이션) 및 ingress 트래픽을 담당하므로, 앱 컨테이너는 Worker 2대에만 배치된다. Amazon ECS는 ECS의 Control Plane이 AWS 관리 영역에 존재하므로, 별도의 Manager 인스턴스가 불필요하다. EC2 2대 모두 앱 Task를 실행하는 워커 역할을 한다. 따라서 공정한 비교 기준은 "앱을 실제로 처리하는 인스턴스 수" 이며, 이 관점에서: Swarm: Manager 1대 + Worker 2대 (총 3대) ↔ ECS: EC2 2대 (총 2대) 양쪽 모두 앱 처리 인스턴스는 2대로 동일하다. 하지만 이 가설이 성립하려면 한 가지를 검증해야 했다. "Swarm Manager가 앱 컨테이너를 실행하지 않고, 실제 처리 부하는 Worker 2대가 전담하는가?" 검증 — 노드별 리소스 사용 현황 측정 Grafana + nodeexporter로 유휴 상태에서 각 노드의 리소스 사용량을 측정했다. 노드 역할 CPU Busy Sys Load RAM 사용량 비고 :::::: Swarm Manager 3.1% 12.5% 25.3% CPU/Load 가장 높음 → 제어 부담 Swarm Worker 1 2.5% 28.5% 33.1% Sys Load 매우 높음 → 실제 연산 수행 Swarm Worker 2 2.5% 4.5% 34.0% 메모리 점유 높음 → 앱 컨테이너 실행 ECSClu 1 2.0% 1.0% 34.9% 안정적 유휴 상태 ECSClu 2 2.1% 2.5% 34.9% ECSClu 1과 거의 동일 패턴 검증 결과 — 가설 입증 측정 결과는 가설을 명확히 지지한다. Manager: RAM 25.3%로 Worker(3334%)보다 유의미하게 낮고, Sys Load도 낮다. 앱 컨테이너를 실행하지 않고 클러스터 제어 부담만 지고 있음이 확인됐다. Worker 1·2: RAM 3334% 점유 및 높은 Sys Load → 실제 앱 컨테이너들이 Worker 노드에 배치되어 자원을 점유하고 있음. ECSClu 1·2: RAM 34.9%로 Swarm Worker와 유사한 수준 → 동일한 앱 처리 부담을 지고 있음. 결론: "Swarm 3대(Manager + Worker 2) vs ECS 2대"는 공정한 비교다. 앱을 실제로 처리하는 데이터 플레인은 양쪽 모두 2대이며, ECS는 관리형 서비스이므로 Manager 인스턴스 비용 없이 동일한 처리 용량을 확보할 수 있다. 실험 1 — 컨테이너 장애 복구 테스트 테스트 방법 runrecoverytest.py 스크립트를 각 노드에 대해 실행해 측정을 자동화했다. SSH로 원격 접속하여 docker kill로 컨테이너를 강제 종료하고, Prometheus API를 5초 간격으로 폴링해 컨테이너 수가 다시 EXPECTEDREPLICAS(1) 이상으로 복구되는 시점을 감지한다. [측정 흐름] kill 명령 실행 (killepoch 기록) → 5초 간격 Prometheus 폴링 (containerstarttimeseconds 기반) → 새 컨테이너의 starttime이 killepoch 이후임을 확인 → 복구 시각 기록 (recoveryseconds = recoveredepoch killepoch) → 결과를 recoverysummary.csv / recoveryraw.csv 에 저장 Prometheus 쿼리 구조 Swarm: containerlabelcomdockerswarmservicename="myapp" 으로 서비스 단위 집계 ECS: name="ecstoy." 정규식으로 Task 컨테이너 집계 복구 판단: max(containerstarttimeseconds{...}) 값이 kill 시점 이후로 갱신되었는지 확인 Docker Swarm Test ※ 그라파나 대시보드의 시간축은 UTC 기준으로 표시되며, 실제 테스트 실행 시각(KST, UTC+9)과 9시간 차이가 있습니다. ECS Test ※ 그라파나 대시보드의 시간축은 UTC 기준으로 표시되며, 실제 테스트 실행 시각(KST, UTC+9)과 9시간 차이가 있습니다. 전체 측정 결과 (recoverysummary.csv) 총 8회 테스트 수행 (Swarm 4회, ECS 4회). 테스트 ID 플랫폼 kill 시각 복구 시각 복구 시간(초) 결과 :: swarmworker120260406 Swarm 14:32:27 14:32:46 19.08초 ✅ success swarmworker220260406 Swarm 15:34:42 15:35:01 19.19초 ✅ success ecs120260407 ECS 02:42:49 — timeout ❌ fail ecs220260407 ECS 03:19:07 03:30:25 677.63초 (약 11분) ✅ success swarmworker120260411 Swarm 13:50:25 13:50:46 21.20초 ✅ success swarmworker220260411 Swarm 13:54:44 13:55:05 21.21초 ✅ success ecs120260411 ECS 13:56:26 14:13:54 1047.99초 (약 17분) ✅ success ecs220260411 ECS 14:17:25 14:40:39 1394.02초 (약 23분) ✅ success 플랫폼별 요약 구분 측정 횟수 평균 복구 시간 범위 성공률 ::::: Docker Swarm 4회 약 20초 19.08 21.21초 4/4 (100%) Amazon ECS 4회 약 1,040초 (약 17분) 677초 1,394초 (1회 timeout 제외) 3/4 (75%) Swarm은 4회 모두 1921초로 일관된 복구를 보인 반면, ECS는 677초1,394초로 회차마다 편차가 크고, 1회는 180초 timeout 내에 복구 자체를 실패했다. ECS timeout 케이스(ecs120260407)는 스크립트 제한(180초) 때문에 실패로 기록됐지만, 이후 테스트에서 같은 노드가 1,047초 만에 복구된 것으로 보아 실제 복구는 이뤄졌으나 측정 범위를 벗어난 것으로 판단된다. 원인 분석 (ECS 복구 지연) ECS의 복구 지연은 단순한 성능 문제가 아니라 구조적 설계 차이에서 비롯된다. ① ALB Health Check 대기 (가장 큰 원인) 체크 주기: 기본 30초 정상 임계 횟수: 기본 5회 최소 소요: 30초 × 5회 = 150초(2분 30초) ② Deregistration Delay (Connection Draining) 기존 컨테이너 등록 해제 대기: 기본 300초(5분) 이론 합계: 5분 + 2분 30초 ≈ 7분 30초 실측값(677초1,394초)은 이 이론치보다도 훨씬 큰데, 이는 테스트 당시 EC2 자원 여유 부족으로 새 Task 생성 자체가 지연됐기 때문으로 추정된다. ③ ECS Agent 폴링 주기 EC2 내 ECS Agent는 실시간 Push가 아닌 주기적 Poll 방식으로 AWS Control Plane과 통신 Swarm 대비 수초수십초 추가 지연 발생 ④ ECS 회차별 편차의 원인 실험 당시 EC2 t3.small(2GB)에 ECS Agent, cAdvisor, nodeexporter가 상주 중이었고, 새 Task 생성 시 메모리 여유가 부족한 경우 배치 자체가 지연됐다. 이는 ECS의 Resource reservation 기반 스케줄링 특성상, 자원이 확보되지 않으면 배치를 아예 거부하는 구조에서 비롯된다. 최적화 가능 방향 설정 항목 기본값 최적화 값 효과 Deregistration Delay 300초 30초 이하 대기 시간 대폭 단축 Health Check Interval 30초 10초 확인 주기 단축 Healthy Threshold 5회 2회 통과 시간 20초로 단축 EC2 메모리 여유 확보 Task 0.25vCPU/0.5GB 인스턴스 업그레이드 or Task 스펙 조정 Task 배치 지연 해소 실험 2 — CPU 부하 오토스케일링 테스트 부하 시나리오 (k6 stresstest.js) 시나리오: rampingarrivalrate 워밍업(30→2분) → CPU 상승(60→3분) → 70% 근접(80→3분) → 유지(80, 5분) → 추가 확장(100, 3분) → 유지(100, 5분) 오토스케일링 트리거: CPU Utilization 70% 초과 Docker Swarm 오토스케일링 결과 이벤트 시각 내용 ScaleOut 10:56:15 2 → 4 컨테이너 증가 Stabilization 10:56:15 이후 count=4로 안정 유지 ScaleIn 11:00:15 4 → 2 컨테이너 감소 SelfHealing 실험 중 1→0→1 컨테이너 재생성 확인 Amazon ECS 오토스케일링 결과 및 이슈 ECS에서는 의도한 ScaleOut 대신 SelfHealing(Task 교체) 현상이 반복됐다. 증상 /stress 요청으로 CPU 100% → /health 지연 → ALB Health Check 실패 ECS가 Unhealthy Task를 종료 후 새 Task 생성 (Desired Count 유지) container=error 로그 반복 확인 원인 부하가 한쪽 컨테이너에 집중 → 전체 평균 CPU가 낮아 오토스케일링 미충족 bridge 모드 고정 hostPort(80) → 동일 인스턴스에 복수 Task 배치 불가 조치 및 awsvpc 전환 후 네트워크 모드: bridge → awsvpc 전환 Target Group 타입: instance → IP 타입 재생성 부하 방식: constantarrivalrate → rampingarrivalrate로 변경 전환 후 2→3 증가는 확인됐으나 Task 1개가 종료되는 현상 재발. 실험 시간 제약으로 완전한 안정화 달성 전 종료. 주요 트러블슈팅 기록 T1. Bridge 모드 고정 hostPort Scaleout 실패 내용 증상 Desired Count 34로 변경해도 Running Task는 2개에서 증가 안 함 원인 hostPort=80 고정 시 EC2 한 대에 동일 포트 컨테이너 2개 배치 불가 조치 hostPort=0(동적 포트) 변경 → 이후 awsvpc + IP Target Group으로 전환 T2. Target Group 설정 오류 → Health Check 실패 → SelfHealing 오해 발생 흐름 bridge 모드에서 hostPort 고정 → Scaleout 시도 시 포트 충돌로 신규 Task 배치 실패 → awsvpc 전환 결정 → 기존 instance 타입 Target Group을 ip 타입으로 변경 시도 → AWS 정책상 불가(immutable) → Target Group 신규 생성했으나 포트 설정 미스 → ALB가 Task를 Unhealthy로 판정 → ECS가 Unhealthy Task 종료 후 새 Task 생성 반복 → Task가 죽었다 살아나는 걸 보고 Scaleout으로 오해 → 실제로는 SelfHealing(장애 교체) 이었음 내용 증상 Scaleout을 기대했으나 기존 Task가 종료되고 새 Task가 생성되는 패턴 반복 근본 원인 instance 타입 Target Group → awsvpc 전환 후 ip 타입으로 재생성 필요. AWS ALB Target Type은 생성 후 변경 불가(immutable)이므로 신규 생성했으나 포트 설정 불일치로 Health Check 계속 실패 조치 Target Group ip 타입으로 재생성 (port 8080, /health) → ALB Security Group에서 8080 인바운드 허용 확인 → /stress?timeout=10 → timeout=1로 부하 완화 T3. Grafana 컨테이너 수 과다 표시 내용 증상 ECS Running Task 2개인데 Grafana에서 45개로 표시 원인 awsvpc 모드에서 Task마다 internalecspause 컨테이너 자동 생성 조치 PromQL에 containerlabelcomamazonawsecscontainername="toysb" 필터 추가 비교 분석 핵심 지표 비교 항목 Docker Swarm Amazon ECS 설계 목적 경량 오케스트레이션 클라우드 운영 플랫폼 복구 시간 약 21초 약 7분 (기본 설정) 스케일링 반응 즉시 (로컬 스케줄러) 수십초수분 (CloudWatch 기반) 스케줄링 방식 Besteffort (공격적) Resource reservation (보수적) 오버커밋 가능 거의 불가 네트워크 오버헤드 낮음 (bridge/overlay) 높음 (ENI, ALB, Target Group) 운영 복잡도 낮음 높음 안정성 상대적으로 낮음 높음 모니터링 통합 직접 구성 필요 CloudWatch 기본 연동 왜 ECS가 느린가 (구조적 이해) [Docker Swarm 복구 흐름] 컨테이너 종료 감지 (Docker Engine 내장) → 즉시 재스케줄링 (로컬 의사결정) → 컨테이너 Running → 즉시 트래픽 수신 ≈ 수초20초 [Amazon ECS 복구 흐름] Docker Engine 장애 감지 → ECS Agent 전달 → AWS Control Plane 보고 (네트워크 왕복) → 새 Task 생성 → ENI 할당 → ALB Target Group 등록 → Health Check 대기 (30초 × 5회 = 150초) → Deregistration Delay 대기 (300초) ≈ 7분+ ECS의 느림은 "안전한 트래픽 전환을 보장하는 설계"의 결과다. 💡 프로젝트를 통해 얻은 핵심 인사이트 1. 동일 환경 비교의 어려움 — ECS는 불리한 조건에서 측정됐다 ECS를 Swarm 수준에 맞추기 위해 세팅 초기에 Task 리소스를 0.25 vCPU/0.5GB로 제한하자 컨테이너 불안정, 메모리 부족, GC 문제가 발생했다. "비교를 공정하게 만들려다 ECS의 장점을 모두 제거하는" 역설을 경험했다. 이번 실험에서 측정된 ECS의 느린 복구 시간(677초1,394초)은 ECS 자체의 한계가 아니라 실험 조건의 한계다. [이번 실험의 ECS 조건 — 불리한 환경] EC2 t3.small (2GB) 위에 ECS Agent + cAdvisor + nodeexporter 상주 → Task 리소스를 0.25vCPU/0.5GB로 억지로 제한 → 메모리 여유 부족으로 새 Task 배치 자체가 지연 → Swarm 비교를 위해 ECS 본래 설계 방식을 벗어난 구성 [ECS 본래 용도로 사용했다면] Fargate 사용 → EC2 관리 불필요, AWS가 미리 준비한 자원 풀에서 즉시 컨테이너 주입 → Task 배치 지연 없음 → Health Check 설정 튜닝 (Interval 10초, Threshold 2회) 시 복구 시간 20초대도 가능 즉, "ECS가 느리다"가 아니라 "EC2 위에 억지로 올린 ECS가 느렸다" 는 것이 정확한 표현이다. Fargate로 ECS 본래 용도대로 운영했다면 복구 속도 자체는 Swarm과 크게 다르지 않을 수 있다. 2. 비교 관점의 전환 Before After 단순 성능 비교 (속도) 운영 전략 비교 (적합한 환경의 차이) "ECS가 느리다" "EC2 위에 억지로 올린 ECS가 느렸다" "Swarm이 더 좋다" "온프레미스엔 Swarm, 클라우드엔 ECS(Fargate)" 3. 실무 관점에서의 선택 기준 ECS가 Swarm보다 낫다거나, 기업이 Swarm을 버리고 ECS를 선택한다는 식의 결론은 정확하지 않다. 실제로는 인프라 환경(온프레미스 vs 클라우드 온디맨드) 에 따라 각각이 적합한 상황이 다르다. 상황 적합한 선택 이유 온프레미스 / 자체 서버 보유 Docker Swarm 클라우드 벤더 종속 없이 직접 소유한 서버에서 경량 오케스트레이션 운영 가능. AWS API 호출 비용·지연 없음. AWS 클라우드 온디맨드 Amazon ECS ALB·CloudWatch·IAM·VPC 등 AWS 에코시스템과 완전 통합. 인프라를 직접 소유하지 않고 사용한 만큼만 과금. 클라우드지만 벤더 독립이 중요 Docker Swarm 멀티클라우드 또는 이식성이 중요한 환경에서는 Swarm이 유리. 서버리스 지향, 인프라 관리 최소화 ECS (Fargate) EC2 자체를 관리할 필요 없이 컨테이너 단위로 과금·운영 가능. 이 프로젝트를 통해 깨달은 핵심은, "어느 쪽이 더 좋은가"가 아니라 "우리 인프라 환경이 온프레미스냐 클라우드냐에 따라 자연스럽게 선택이 갈린다" 는 점이다. 온프레미스 서버를 보유한 조직이 굳이 ECS를 쓸 이유가 없고, AWS 위에서 운영하는 조직이 굳이 Swarm을 직접 관리할 이유도 없다. 4. 트러블슈팅 역량 단순 기능 구현을 넘어, 네트워크 모드 선택이 확장성에 미치는 영향, ALB Health Check가 복구 시간을 결정하는 메커니즘, ECS Agent 폴링 방식의 한계 등 인프라 내부 동작 원리를 실험을 통해 직접 검증했다. 기술 스택 구분 기술 컨테이너 오케스트레이션 Docker Swarm, Amazon ECS (EC2 Launch Type) 애플리케이션 Spring Boot (Java) 인프라 AWS EC2 (t3.small), ALB, VPC, Target Group 모니터링 Prometheus, Grafana, cAdvisor, nodeexporter 부하 테스트 k6 (rampingarrivalrate), Python (autoscaletest.py) 네트워크 bridge, overlay, awsvpc, ENI 회고 및 결론 본 프로젝트는 "완벽한 비교 실험"보다 더 가치 있는 것을 남겼다. 단순히 수치를 비교하는 과정에서, 두 시스템이 서로 다른 문제를 풀기 위해 설계됐다는 점을 발견했다. ECS를 Swarm처럼 다운그레이드해서 비교하는 것 자체가 잘못된 접근이었음을 실험 도중 깨달았고, 그 깨달음 자체가 이 프로젝트의 핵심 성과다. Docker Swarm은 빠르고 단순한 오케스트레이터, Amazon ECS는 느리지만 안정적인 클라우드 운영 플랫폼이다.

May 11, 2026오케스트레이션
ECS vs Docker Swarm 컨테이너 오케스트레이션 비교 프로젝트

On-Premise Kubernetes 클러스터 구축 및 CI/CD 파이프라인 자동화 프로젝트

OnPremise Kubernetes 클러스터 구축 및 CI/CD 파이프라인 자동화 프로젝트 기간: 2026.04.27 – 2026.04.30 기술 스택: Kubernetes · Helm · ArgoCD · GitHub Actions · Amazon ECR · AWS 역할: 온프레미스 K8s 클러스터 세팅 / 초기 매니페스트 작성 / Helm Chart 고도화 / CI/CD 파이프라인 구축 목차 1. 프로젝트 개요 2. 전체 아키텍처 3. 온프레미스 K8s 클러스터 구성 4. 초기 매니페스트 작성 5. Helm Chart 고도화 6. ECR 프라이빗 레지스트리 연동 트러블슈팅 7. ArgoCD 기반 CI/CD 파이프라인 구축 8. 배포 검증 결과 9. 회고 및 개선 방향 1. 프로젝트 개요 단순히 애플리케이션을 컨테이너로 실행하는 수준을 넘어서, 실제 운영 환경에 가까운 배포 자동화 구조를 직접 설계하고 구현하는 것을 목표로 했습니다. 온프레미스 VMware 환경에 Kubernetes 클러스터를 직접 구성하고, 원시 YAML 매니페스트에서 출발해 Helm Chart로 고도화한 뒤 GitHub Actions → ECR → ArgoCD 로 이어지는 GitOps 기반 CI/CD 파이프라인까지 완성했습니다. 핵심 목표: 쿠버네티스 핵심 리소스(Deployment, Service, Ingress, Namespace)를 실제 클러스터에서 직접 경험 Prod / Dev 환경을 노드 단위로 분리하여 운영 안정성 확보 Helm Chart로 환경별 설정을 코드로 관리 GitOps 방식으로 배포 이력 추적 및 자동화 2. 전체 아키텍처 개발자 코드 Push │ ▼ GitHub Actions (CI) │ Docker 이미지 빌드 │ Amazon ECR Push ▼ ArgoCD (CD / GitOps) │ Git 상태 감지 → Sync ▼ OnPremise K8s 클러스터 (VMware 211.183.3.0/24) ├── Master Node (211.183.3.200) ├── Prod: worker1 (211.183.3.210) + worker2 (211.183.3.220) └── Dev: devworker (211.183.3.230) 외부 접근 흐름: 사용자 → Route53(prod.dongkyu.cloud) → EC2 nginx 리버스 프록시 → Tailscale 터널 → K8s NGINX Ingress Controller (NodePort 31018) → Service → Pod 3. 온프레미스 K8s 클러스터 구성 31. 클러스터 노드 구성 역할 호스트명 IP 환경 Master toymaster 211.183.3.200/24 제어 플레인 Worker toyworker1 211.183.3.210/24 Prod Worker toyworker2 211.183.3.220/24 Prod Worker devtoyworker 211.183.3.230/24 Dev kubeadm v1.30.14, Ubuntu 24.04, Flannel CNI로 구성했으며 Pod CIDR은 10.244.0.0/16을 사용했습니다. 32. Prod / Dev 환경 분리 단순히 Namespace만 분리하면 Pod가 어느 노드에든 스케줄링될 수 있습니다. 노드 라벨과 nodeSelector를 조합해 Prod Pod는 Prod 노드에만, Dev Pod는 Dev 노드에만 배치되도록 강제했습니다. bash kubectl create namespace dev kubectl create namespace prod kubectl label node toyworker1 env=prod kubectl label node toyworker2 env=prod kubectl label node devtoyworker env=dev yaml Deployment spec 일부 spec: template: spec: nodeSelector: env: prod dev 환경은 env: dev 항목 Dev Prod Namespace dev prod Node 라벨 env=dev env=prod 도메인 dev.dongkyu.cloud prod.dongkyu.cloud 레플리카 1 2 4. 초기 매니페스트 작성 41. 애플리케이션 구조 Backend(Spring Boot, 8080 포트)와 Frontend(Nginx, 80 포트)를 각각 Deployment + Service로 정의하고, NGINX Ingress Controller로 외부 라우팅을 구성했습니다. yaml appdeploy.yml — backend Deployment (일부) apiVersion: apps/v1 kind: Deployment metadata: name: backenddep spec: replicas: 2 selector: matchLabels: app: backend template: spec: nodeSelector: env: prod imagePullSecrets: name: ecrsecret containers: name: backend image: 431538665162.dkr.ecr.apnortheast2.amazonaws.com/backend:latest ports: containerPort: 8080 42. Ingress 라우팅 설계 /api/(.) 경로는 backend로, 나머지 경로(/?(.))는 frontend로 분기합니다. yaml ingress.yml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: appingress annotations: nginx.ingress.kubernetes.io/rewritetarget: /$1 nginx.ingress.kubernetes.io/useregex: "true" spec: ingressClassName: nginx rules: host: prod.dongkyu.cloud http: paths: path: /api/(.) pathType: ImplementationSpecific backend: service: name: backend port: number: 8080 path: /?(.) pathType: ImplementationSpecific backend: service: name: frontend port: number: 80 외부 요청 경로: prod.dongkyu.cloud └─ DNS: 211.183.3.231 (Ingress Controller External IP) └─ NGINX Ingress: Host 헤더 확인 ├─ /api/ → backend Service → Pod └─ / → frontend Service → Pod 5. Helm Chart 고도화 초기 매니페스트는 환경마다 중복 YAML을 작성해야 했습니다. Helm Chart로 템플릿화하여 하나의 Chart를 values 파일만 바꿔 dev/prod에 재사용하는 구조로 개선했습니다. GitHub: LDK511/aws13k8sproject 51. 디렉토리 구조 helm/ ├── backend/ │ ├── Chart.yaml │ ├── templates/ │ │ ├── deployment.yaml │ │ ├── service.yaml │ │ └── ingress.yaml │ ├── values.yaml 공통 기본값 │ ├── valuesdev.yaml Dev 오버라이드 │ └── valuesprod.yaml Prod 오버라이드 └── frontend/ ├── Chart.yaml ├── templates/ │ ├── deployment.yaml │ └── service.yaml ├── values.yaml ├── valuesdev.yaml └── valuesprod.yaml 52. values.yaml 구조 yaml backend/values.yaml env: prod imagePullSecret: ecrsecret replicaCount: 2 image: repository: 431538665162.dkr.ecr.apnortheast2.amazonaws.com/backend tag: "latest" service: port: 8080 ingress: enabled: true className: nginx host: prod.dongkyu.cloud yaml backend/valuesdev.yaml — dev 환경 오버라이드 env: dev replicaCount: 1 image: tag: "latest" ingress: host: dev.dongkyu.cloud 53. Helm 템플릿 — Deployment yaml backend/templates/deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: backend spec: replicas: {{ .Values.replicaCount }} selector: matchLabels: app: backend template: spec: nodeSelector: env: {{ .Values.env }} imagePullSecrets: name: {{ .Values.imagePullSecret }} containers: name: backend image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" ports: containerPort: 8080 54. 배포 명령 흐름 bash 문법 검사 helm lint ./backend && helm lint ./frontend 렌더링 미리보기 (실제 배포 전 확인) helm template backenddev ./backend f ./backend/valuesdev.yaml Dev 배포 helm install backenddev ./backend n dev f ./backend/valuesdev.yaml helm install frontenddev ./frontend n dev f ./frontend/valuesdev.yaml Prod 배포 helm install backendprod ./backend n prod f ./backend/valuesprod.yaml helm install frontendprod ./frontend n prod f ./frontend/valuesprod.yaml 업데이트 helm upgrade backenddev ./backend n dev f ./backend/valuesdev.yaml Before vs After 비교: 항목 초기 매니페스트 Helm Chart 고도화 후 환경별 파일 dev/prod 각각 별도 YAML values 파일만 교체 재사용성 없음 (복사·붙여넣기) 하나의 Chart 재사용 배포 이력 수동 관리 helm history로 추적 롤백 이전 파일 재적용 helm rollback 1커맨드 6. ECR 프라이빗 레지스트리 연동 트러블슈팅 문제 상황 ECR 프라이빗 저장소에서 이미지를 Pull할 때 ImagePullBackOff 오류가 지속적으로 발생했습니다. 터미널에서 docker login을 성공했음에도 동일한 에러가 반복되었습니다. 근본 원인 분석 원인 설명 인증 주체 분리 터미널의 docker login 정보를 Kubelet이 자동으로 공유하지 않음 저장 위치 차이 로그인 정보는 /.docker/config.json (유저 홈) → Kubelet은 참조 불가 노드 전파 안 됨 마스터 노드 인증은 워커 노드에 전파되지 않음 런타임 차이 최신 K8s는 containerd 사용 → Docker 로그인 정보와 미호환 해결 방법 ECR 로그인 토큰을 Kubernetes Secret으로 등록하고 Deployment에 명시적으로 연결합니다. 핵심 원리: IAM 키는 "열쇠를 만들 권한"이고, 실제로 이미지를 Pull하는 Kubelet이 열 수 있는 "물리적 열쇠"(ecrsecret)를 네임스페이스에 직접 비치해야 합니다. bash Step 1: ECR 인증 토큰으로 K8s Secret 생성 (유효시간 12시간) kubectl create secret dockerregistry ecrsecret \ dockerserver=431538665162.dkr.ecr.apnortheast2.amazonaws.com \ dockerusername=AWS \ dockerpassword=$(aws ecr getloginpassword region apnortheast2) yaml Step 2: Deployment에 imagePullSecrets 명시 spec: template: spec: imagePullSecrets: name: ecrsecret containers: name: backend image: 431538665162.dkr.ecr.apnortheast2.amazonaws.com/backend:latest 운영 개선 포인트: ECR 토큰은 12시간마다 만료됩니다. 실제 운영 환경에서는 CronJob을 통한 자동 갱신이 필요합니다. 7. ArgoCD 기반 CI/CD 파이프라인 구축 71. CI/CD 흐름 개발자 Push (main → Prod, develop → Dev) │ ▼ GitHub Actions ├── Docker 이미지 빌드 └── Amazon ECR Push (이미지 태그: commit SHA) │ ▼ ArgoCD (Git 저장소 감지) │ helm/backend/values.yaml의 image.tag 변경 감지 ▼ K8s 클러스터 자동 Sync ├── dev namespace ← develop 브랜치 └── prod namespace ← main 브랜치 72. ArgoCD 설치 (Helm) bash helm repo add argo https://argoproj.github.io/argohelm helm repo update helm install argocd argo/argocd \ namespace argocd \ createnamespace \ set server.service.type=NodePort \ set server.service.nodePortHttps=31443 Tailscale IP를 통해 ArgoCD UI에 접근합니다: https://100.100.150.8:31443 73. Application CRD 구성 ArgoCD에게 "어떤 Git 저장소의 어떤 Chart를 어떤 네임스페이스에 배포할 것인가"를 선언하는 Application CRD를 작성합니다. yaml argocd/backenddev.yaml apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: backenddev namespace: argocd spec: project: default source: repoURL: https://github.com/LDK511/aws13k8sproject.git path: helm/backend targetRevision: develop Dev → develop 브랜치 helm: valueFiles: valuesdev.yaml destination: server: https://kubernetes.default.svc namespace: dev syncPolicy: automated: Git 변경 감지 시 자동 배포 selfHeal: true 클러스터 상태가 Git과 달라지면 자동 복구 prune: true Git에서 삭제된 리소스 자동 제거 총 4개의 Application CRD를 작성하여 backenddev, backendprod, frontenddev, frontendprod 모두 자동 배포가 가능하도록 구성했습니다. bash kubectl apply f argocd/backenddev.yaml kubectl apply f argocd/backendprod.yaml kubectl apply f argocd/frontenddev.yaml kubectl apply f argocd/frontendprod.yaml 74. 브랜치 전략 연계 브랜치 ArgoCD Target 배포 환경 네임스페이스 main main Prod prod develop develop Dev dev 8. 배포 검증 결과 81. Pod 배치 확인 83. Ingress 라우팅 검증 bash kubectl get ingress A NAMESPACE NAME CLASS HOSTS ADDRESS PORTS dev appingress nginx dev.dongkyu.cloud 211.183.3.230 80 prod appingress nginx prod.dongkyu.cloud 211.183.3.230 80 84. ArgoCD 동기화 상태 4개 Application(backenddev, backendprod, frontenddev, frontendprod) 모두 Healthy / Synced 상태로 정상 동작을 확인했습니다. 9. 회고 및 개선 방향 잘 된 점 초기 단순 매니페스트에서 Helm Chart 고도화, CI/CD 연동까지 전체 배포 사이클을 한 번에 경험 nodeSelector를 통한 Prod/Dev 노드 분리로 리소스 격리 구현 ECR ImagePullBackOff 트러블슈팅 과정에서 K8s 인증 체계(Secret, Kubelet, containerd)에 대한 깊은 이해 획득 GitOps 방식으로 배포 이력 추적 및 selfHeal을 통한 클러스터 자동 복구 경험 개선할 점 / 향후 계획 개선 항목 이유 ECR Secret 자동 갱신 CronJob 12시간 토큰 만료 문제 해결 TLS/HTTPS 적용 (certmanager) 현재 HTTP로만 서비스 중 HPA (Horizontal Pod Autoscaler) 트래픽 기반 자동 스케일링 Prometheus + Grafana 모니터링 강화 Pod/노드 메트릭 시각화 이 프로젝트의 핵심 가치는 "서비스를 만드는 것"이 아니라 "서비스가 어떻게 운영되는가"를 직접 설계하고 증명한 것입니다. DNS → Ingress → Service → Pod로 이어지는 네트워크 흐름, ECR 인증 구조, Helm 기반 환경 분리, GitOps 배포 방식은 클라우드/DevOps 직무에서 즉시 활용 가능한 역량입니다.

May 10, 2026kubernetes
On-Premise Kubernetes 클러스터 구축 및 CI/CD 파이프라인 자동화 프로젝트