필터:#ECR×

[Docker] Docker Swarm 정리

Docker를 사용하다 보면 단일 서버만으로는 한계가 생긴다. 트래픽이 몰리거나 서버가 죽었을 때 대응이 안 되기 때문이다. 이런 문제를 해결하기 위해 나온 게 바로 Docker Swarm이다. Docker vs Docker Compose vs Docker Swarm Docker — 단일 호스트, 단일 컨테이너 Docker Compose — 단일 호스트, 여러 종류의 컨테이너 Docker Swarm — 다수의 호스트(manager + workers)에 여러 종류의 컨테이너 여러 개의 호스트 묶음을 "클러스터" 라고 한다. docker swarm은 도커에서 만든 컨테이너 오케스트레이션 툴이다. 오케스트레이션이란? 오케스트레이션은 다수의 호스트로 구성된 클러스터에서 다수의 컨테이너를 관리하는 기술이다. (스케일링, 헬스체크, 로드밸런서(트래픽제어)) 혼자서 악기 하나를 연주하는 건 쉽지만, 수십 명의 연주자가 합주를 하려면 지휘자가 필요하다. docker swarm은 그 지휘 역할을 하는 것이다. 클러스터링 : 여러 대의 물리 서버를 하나의 가상 리소스로 통합한다. 스케일링 : 명령 한 줄로 컨테이너 개수를 수십 개로 늘리거나 줄인다. 상태 관리(Selfhealing) : 어떤 서버가 죽어서 컨테이너가 꺼지면 살아있는 다른 서버에 자동으로 그 컨테이너를 다시 살려낸다. 로드밸런싱(트래픽제어) : 들어오는 요청을 여러 서버에 떠 있는 컨테이너들에게 골고루 분산한다. docker swarm 클러스터 구성 실습에서 사용한 클러스터 구성은 아래와 같다. manager : 211.183.3.100 worker1 : 211.183.3.110 worker2 : 211.183.3.120 manager node 구성 manager 노드는 control plane 역할을 한다. manager에서 아래 명령으로 swarm을 초기화한다. bash docker swarm init 초기화 후 출력되는 join 토큰을 파일로 저장해두자. bash vi token manager 노드와 worker1,2 노드가 연결이 되었다. docker service 다수의 호스트에 한 가지 종류의 컨테이너를 한 개 이상 생성한다. docker run으로 컨테이너를 생성하는 것과 비슷한 개념이다. bash docker service create replicas 2 p 5858:80 name myweb oolralra/ipnginx replicas : 복제본, 컨테이너를 두 개 띄우겠다는 의미. ipnginx : 자신(컨테이너)의 IP를 출력하는 이미지. 로드밸런싱이 잘 되고 있다. bash docker service ps myweb 따로 컨테이너 배치 제한을 두지 않았기 때문에 현재 총 3개의 노드 중 아무 곳에 배치되었을 것이다. bash docker network inspect ingress docker swarm을 구성하는 순간, 오버레이 네트워크도 함께 구성된다. 오버레이 네트워크에 의해 다른 호스트에 있는 컨테이너들끼리도 서로 통신이 가능하다. 만든 web1과 web2 컨테이너를 하나로 묶어주는, 눈에는 보이지 않는 접속 포인트가 생긴다. (LB와 유사) docker stack deploy docker compose와 비슷하다. 컴포즈 파일이 필요하다. 기존의 컴포즈 파일과 거의 문법이 흡사하지만 조금 다른 부분이 있다. 스택이란 여러 종류의 서비스의 묶음이라고 생각하면 된다. bash mkdir /swarm cd /swarm vi dockercompose.yml yml services: ipnginx: image: oolralra/ipnginx deploy: replicas: 2 placement: constraints: [node.role != manager] 노드의 역할이 manager인 경우는 배치하지 않겠다. 워커노드에만 컨테이너를 배치하겠다. bash docker stack deploy c dockercompose.yml ipstack bash docker stack rm ipstack 스택 삭제 명령어. visualizer — 컨테이너 시각화 각 호스트의 컨테이너를 시각화하는 컨테이너 이미지 = visualizer bash vi visual.yml yml services: visual: image: 61.254.18.30:5000/visualizer ports: '5656:8080' volumes: /var/run/docker.sock:/var/run/docker.sock deploy: placement: constraints: [node.role == manager] bash docker stack deploy c visual.yml vis insecure 설정이 안 되어있으면 해주자. bash vi /etc/docker/daemon.json json { "insecureregistries": ["211.183.3.100:5000","61.254.18.30:5000"] } bash systemctl restart docker 211.183.3.100:5656으로 접속. 사설 저장소에서 이미지 가져오기 61.254.18.30:5000/ipnginx:latest 이미지로 컴포즈 파일을 수정해서 replicas 4개로 5959 포트로 publish 해보자. 저장소 수정 후 insecure 설정도 바꿔줘야 한다. 사설 저장소에 이미지가 존재함에도 불구하고 컨테이너가 띄워지지 않는다. 사설 저장소 insecure 설정이 manager 노드에만 되어있는데, 실질적으로 컨테이너 이미지를 땡겨오는 주체는 worker1과 worker2이기 때문이다. worker1과 worker2에도 insecure 설정을 해주면 된다. bash vi /etc/docker/daemon.json json { "insecureregistries": ["211.183.3.100:5000","61.254.18.30:5000"] } bash docker stack deploy c dockercompose.yml pstack ECR 퍼블릭 갤러리 이미지로 WordPress + MySQL 스택 배포 DB는 manager에 한 개만 띄우고, wordpress는 worker 노드에 2개를 띄워서 12345 포트로 접속 가능하게 해보자. bash vi wordpress.yml yml services: mywp: image: public.ecr.aws/docker/library/wordpress:php8.1apache ports: '12345:80' deploy: replicas: 2 placement: constraints: [node.role == worker] environment: WORDPRESSDBHOST: dbdb WORDPRESSDBUSER: wpuser WORDPRESSDBPASSWORD: '1234' WORDPRESSDBNAME: wpdb dependson: dbdb dbdb: image: public.ecr.aws/docker/library/mysql:8 deploy: placement: constraints: [node.role == manager] environment: MYSQLROOTPASSWORD=1234 MYSQLUSER=wpuser MYSQLPASSWORD=1234 MYSQLDATABASE=wpdb 잘 접속된다.

May 12, 2026Docker
[Docker] 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 파이프라인 자동화 프로젝트