로컬환경

(MacBook-M시리즈칩)

docker build -t gamzaa/product-service:1.0 .
docker push gamzaa/product-service:1.0

 

서버(Linux, amd64 아키텍처)에서 이미지 가져오려했는데 실패

docker pull gamzaa/product-service:1.0

# 1.0: Pulling from gamzaa/product-service
# no matching manifest for linux/amd64 in the manifest list entries

 

 

원인

CPU 아키텍처 불일치 문제

  • 로컬: Apple Silicon(M2) - ARM64
  • 서버: 일반적인 클라우드 서버 - AMD64(x84_64)

로컬에서 이미지를 빌드할 때 ARM64 아키텍처용으로 빌드함

AMD64 아키텍처 서버에서는 실행할 수 없음

 

해결 방법

Docker Buildx 사용

Docker Buildx
: 여러 플랫폼용 이미지를 동시에 빌드할 수 있는 Docker의 확장 기능, 한 번의 명령으로 여러 아키텍처를 지원하는 이미지 생성

 

docker buildx build --platform linux/amd64,linux/arm64 -t gamzaa/product-service:1.0 --push .
docker pull gamzaa/product-service:1.0 #성공

현재 방식

  • 로컬에서 애플리케이션 빌드하여 JAR 파일 생성
  • Dockerfile(JDK설치, JAR파일 복사 및 실행 명령) 작성
  • 서버에 JAR파일과 Dockerfile 업로드
  • 서버에서 Dockerfile로 도커 이미지 빌드
  • 빌드된 이미지로 도커 컨테이너 실행

 

Docker Hub에 이미지 올려서 배포

 

1. Docker Hub(hub.docker.com) 계정 생성

 

2. 로컬에서 Dockerfile로 도커이미지 빌드

# 사용자이름/이미지이름:태그
docker buildx build --platform linux/amd64,linux/arm64 -t gamzaa/product-service:1.0 --push .

 

3. 로컬에서 Docker CLI 로그인

docker login

 

4. 빌드된 도커이미지를 Docker Hub에 푸시

docker push gamzaa/product-service:1.0

 

 

5. 서버에서 도커이미지 pull

docker pull gamzaa/product-service:1.0
docker run -d --name product-service -p 11002:11002 --restart always gamzaa/product-service:1.0

 

'DevOps > Docker' 카테고리의 다른 글

Docker 배포 시 아키텍처 불일치  (0) 2025.05.09
Docker 명령어  (0) 2025.05.08
Docker: 컨테이너를 통한 효율적인 개발 환경 관리  (1) 2025.04.08

 

API Gateway가 User Service에 연결할 수 없는 문제 발생

500 Server Error for HTTP POST "/v1/auth/signup"
io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: /172.17.0.2:10001

 

 

환경구성>

Eureka Server :  Lightsail 인스턴스 (43.203.45.192:8761)

API Gateway : EC2 인스턴스 (3.34.137.212:10000)

User Service : EC2 인스턴스 (3.35.3.139:10001)

→ 각 서비스는 Docker 컨테이너로 실행됨

 

User-service의 application.yml

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://43.203.45.192:8761/eureka/
  instance:
    prefer-ip-address: true

 

 

원인

Dockers 내부 IP vs EC2 IP 문제

  • User Service가 Eureka에 등록될 때 IP를 명시하지 않아 내부IP(172.17.0.2)를 등록
  • 내부IP는 해당 EC2 인스턴스 내에서만 유효하므로 다른 EC2에서 접근 불가능
  • API Gateway가 Eureka에서 조회한 User Service IP (172.17.0.2) 주소로 연결을 시도했으나 실패함

 

해결 방법

1) User Service의 Eureka 클라이언트 설정을 수정 - EC2의 실제 IP 주소를 등록하도록 변경

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://43.203.45.192:8761/eureka/
  instance:
    prefer-ip-address: true
    ip-address: 3.35.3.139  # EC2의 실제 IP 주소
    instance-id: ${eureka.instance.ip-address}:${server.port}

 

 

 

 

 

2) 호스트모드

: 컨테이너가 호스트의 네트워크 스택을 직접 사용

-d --network=host

 

docker run -d --network=host --name user-serivce --restart always user-service

 

 

브릿지모드 vs 호스트모드

 

 

 

'DevOps > AWS' 카테고리의 다른 글

AWS Lightsail 배포  (1) 2025.05.07
AWS EC2 배포 (JAR 파일을 이용한 수동 배포)  (0) 2025.05.07

컨테이너 중지

# 특정 컨테이너 중지
docker stop 컨테이너ID_또는_이름

# 실행 중인 모든 컨테이너 중지
docker stop $(docker ps -q)

 

 

컨테이너 삭제
# 특정 컨테이너 삭제
docker rm 컨테이너ID_또는_이름

# 중지된 모든 컨테이너 삭제
docker rm $(docker ps -a -q)

# 컨테이너 강제 삭제 (실행 중인 컨테이너도 삭제)
docker rm -f 컨테이너ID_또는_이름
 
이미지 삭제
# 특정 이미지 삭제
docker rmi 이미지ID_또는_이름:태그

# 태그가 없는(dangling) 이미지 삭제
docker image prune

# 사용하지 않는 모든 이미지 삭제
docker image prune -a
 
한번에 정리
# 중지된 모든 컨테이너, 사용되지 않는 네트워크, 태그가 없는 이미지, 빌드 캐시 삭제
docker system prune

# 사용하지 않는 모든 리소스(볼륨 포함) 삭제
docker system prune -a --volumes

 

1. DB를 한 서버에 Docker compose로 올리기

 

1) 인스턴스 생성

2) SSH 접속 / TimeZone 설정

https://gamza-devlog.tistory.com/37

 

AWS EC2 배포 (JAR 파일을 이용한 수동 배포)

유광열 튜터님 강의- 클라우드: IT 서비스를 운영할 시 필요한 기계들을 온라인으로 대여할 수 있는 서비스- AWS : 아마존에서 서비스하는 클라우드- EC2 : AWS에서 대여해주는 서버용 컴퓨터 (Elastic

gamza-devlog.tistory.com

 

3) Docker 설치

curl -fsSL https://get.docker.com/ | sudo sh

# root 사용자로 전환했을 경우
curl -fsSL https://get.docker.com/ | sh

 

 

4) docker-complse.yml 올리기

# 로컬 터미널 
scp -i /Users/t2024-m0206/Documents/Dev/key/LightsailDefaultKey-ap-northeast-2.pem /Users/t2024-m0206/Documents/git/musinsam/docker-compose.yml ubuntu@IPv4:/home/ubuntu/

 

5) docker compose 실행

docker compose -d
docker ps

 

 

 

 

2. 애플리케이션 배포

 

1) Dockerfile

: 이 애플리케이션을 어떻게 패키징 할지 정의하는 설정 파일

FROM eclipse-temurin:17-jdk-alpine 				# 1. JAVA 17 환경 가져옴
WORKDIR /app							# 2. 컨테이너 내부에 /app 폴더 생성
COPY *.jar app.jar						# 3. JAR 파일을 컨테이너에 복사
EXPOSE 8761							# 4. 8761 포트를 외부에 공개
ENTRYPOINT ["java", "-jar", "/app/app.jar"]			# 5. 컨테이너 시작 시 실행할 명령

 

2) 로컬에서 JAR 파일 빌드

# ./gradlew 파일에 실행 권한 부여 해야 할 수 있음
chmod +x ./gradlew

./gradlew build
# 또는
./gradlew bootJar

 

3) 파일들을 서버로 전송

# eureka-server 디렉토리 만들기
mkdir eureka-server
# 디렉토리의 권한을 수정
sudo chown ubuntu:ubuntu /home/ubuntu/eureka-server
# 로컬
scp -i /Users/t2024-m0206/Documents/Dev/key/LightsailDefaultKey-ap-northeast-2.pem /Users/t2024-m0206/Documents/git/musinsam/eureka-server/build/libs/eureka-server-0.0.1-SNAPSHOT.jar ubuntu@IPv4:/home/ubuntu/eureka-server/ 
scp -i /Users/t2024-m0206/Documents/Dev/key/LightsailDefaultKey-ap-northeast-2.pem /Users/t2024-m0206/Documents/git/musinsam/eureka-server/Dockerfile ubuntu@IPv4:/home/ubuntu/eureka-server/

 

4) 서버에서 Docker 이미지 빌드 및 실행

# Docker 설치
curl -fsSL https://get.docker.com/ | sudo sh

# root 사용자로 전환했을 경우
curl -fsSL https://get.docker.com/ | sh
# eureka-server 디렉토리 이동 후
# Docker 이미지 빌드 (이 과정에서 Java 설치, JAR 파일 복사 등이 자동으로 이루어짐)
docker build -t eureka-server .
# Docker 컨테이너 실행
docker run -d --name eureka-server -p 8761:8761 --restart always eureka-server

# 환경변수 있을 시
docker run -d --name eureka-server -p 8761:8761 --restart always -e 환경변수1=value -e 환경변수2=value eureka-server
 

docker ps

 

 

유광열 튜터님 강의

- 클라우드: IT 서비스를 운영할 시 필요한 기계들을 온라인으로 대여할 수 있는 서비스

- AWS : 아마존에서 서비스하는 클라우드

- EC2 : AWS에서 대여해주는 서버용 컴퓨터 (Elastic Compute Cloud)

- MobaXterm : 클라우드 서버에 접속하기 위한 프로그램 (콘솔 사용 및 파일 관리, putty와 filezilla 합친 느낌)

 

EC2에 리눅스를 설치하면 해당 컴퓨터의 시간대가 초기화(UTC+0)되어 DB에 데이터 업데이트 시 잘못된 시간이 저장될 수 있음

→ 대한민국 서울 시간대로 조정해야 함

 

 

1) 프로젝트 빌드

# ./gradlew 파일에 실행 권한 부여 해야 할 수 있음
chmod +x ./gradlew

./gradlew build
# 또는
./gradlew bootJar

 

 

2) EC2 대여 (인스턴스 생성)

 

- 키 페어 : 서버에 접속할 수 있는 암호 파일

 

- 인바운드 규칙 편집 : 외부에서 서버 컴퓨터로 접근할 수 있는 규칙 정함

해당 프로젝트는 8080포트를 사용하고 있음 외부에서 해당 포트로 접근할 수 있도록 설정해야 함

모든 사람들이 접근할 수 있도록 IP는 0.0.0.0/0으로 설정

 

 

3) SSH 접속

✅ EC2 인스턴스 생성 시 .pem 키 파일 다운로드
✅ 보안 그룹에서 포트 22 (SSH) 열려 있어야 함
✅ 인스턴스 퍼블릭 IPv4 주소 확인

 

1. 키 파일 권한 설정

키 파일 있는 디렉토리 이동 후

chmod 400 first_key.pem # 400: 읽기만 허용

 

2. SSH 접속

OS가 Ubuntu일 경우 유저네임 ubuntu

ssh -i first_key.pem ubuntu@IPv4 주소

 

 

4) TimeZone 설정

관리자(루트) 권한 사용

$ sudo su

현재 시간대 확인

timedatectl

 

 

대한민국 시간대로 변경

timedatectl set-timezone Asia/Seoul

 

 

 

5) JDK 다운로드

 

다운로드 목록 받아옴 (패키지 목록을 업데이트해서 새로운 버전이 있는지 확인)

sudo apt update

 

목록에 openjdk가 있는지 검색

apt-cache search openjdk

 

openjdk-17-jdk  설치

apt install -y openjdk-17-jdk

 

자바가 잘 설치되었는지 확인

java -version

 

 

6) 폴더 만들기 / 배포

mkdir my_app # /home/ubuntu/my_app

 

- 로컬에 있는 mybatis_project-0.0.1-SNAPSHOT.jar 파일을 my_app 디렉터리에 넣기

 

my_app 디렉토리의 권한을 수정해서 ubuntu 사용자에게 쓰기 권한을 부여

sudo chown ubuntu:ubuntu /home/ubuntu/my_app

 

# 로컬 터미널
scp -i /Users/t2024-m0206/Documents/Dev/key/first_key.pem /Users/t2024-m0206/Documents/git/Sparta_CodingClub/AWS/build/libs/mybatis_project-0.0.1-SNAPSHOT.jar ubuntu@IPv4:/home/ubuntu/my_app/

 

ls -l /home/ubuntu/my_app/

 

 

7) 서버 프로그램 실행

 

cd my_app/
nohup java -jar mybatis_project-0.0.1-SNAPSHOT.jar 1>log.out 2>err.out &

nohup: 유저가 콘솔에 접속 중이지 않더라도 서버 실행 유지 (터미널 세션이 끊겨도 계속 실행되도록 해줌)

java -jar : 자바 jar파일 실행 (내부 톰캣용)

파일명 : m + tab 자동완성

1>log.out : 1은 표준 출력, 문제 없을 때 log.out 파일에 로그 기록

2>err.out : 2는 에러 출력, 문제 있을 때 err.out에 출력

&: 백그라운드 실행 (유저가 로그아웃하면 꺼짐. nohup과 같이 사용하면 실행 유지 가능)

 

 

8) 로그 확인

cat err.out
cat log.out
tail -f log.out (파일 내용 중 마지막만 보여줌. -f 붙이면 실시간 업데이트)

 

 

9) 프로세스 확인 및 kill

자바 프로세스 확인

ps -ef | grep mybatis_project-0.0.1-SNAPSHOT.jar

자바 프로세스 아이디 : 3790

프로세스 종료

kill -9 프로세스아이디

개발 작업 순서

개발용 컴퓨터에서 개발 → 테스트 성공 → 서버에 올림

 

배포: 서버(서버용 컴퓨터)에서 프로그램을 실행

 

개발용 컴퓨터와 서버용 컴퓨터에 이 프로그램을 돌릴 수 있는 환경이 동일해야 함

 


 

일반적인 개발 환경의 문제점

개발 환경을 하나의 집으로 비유했을 때, 이 집에는 Java, MySQL 같은 기술 스택이 설치된다는 것은 이것들이 입주자 처럼 생활 하는 것과 같음.

한 가지 서비스만 개발한다면 큰 문제가 없지만,

  • 서비스A는 Java 17버전이 필요한데, 서비스B는 Java 21버전을 필요로 함
  • 두 버전을 동시에 설치하면 충돌이 발생하고 복잡한 설정이 필요함
  • 다른 기술들도 버전 충돌 문제가 발생할 수 있음

결국 한 집에서 여러 프로젝트를 진행하면 입주자들이 함께 지내기 어려워짐.

 

 

가상화(Virtualization)의 한계

이 문제를 해결하기 위해 가상화 기술(VirtualBox, VMWare, Parallels 등)을 사용할 수 있음. 하나의 OS안에 또 다른 OS를 설치하여 서비스별로 분리된 환경을 제공함.

  • 컴퓨터 자원(CPU, 메모리)이 각 가상 환경마다 고정적으로 할당되어 비효율적
  • 각 가상 환경마다 별도의 OS가 필요해 저장 공간 낭비
  • OS기능들(부엌, 화장실 등)이 중복되어 자원 낭비

특히 성능이 중요한 서버 환경에서는 이러한 낭비가 더 큰 문제가 됨.

 

 

도커 컨테이너

도커는 컨테이너 개념을 통해 이 문제를 해결함.

  1. 격리된 작업 공간 : 컨테이너는 대저택 안의 업무 공간과 같음. 각 근무자가 자신만의 공간에서 일할 수 있음
  2. 자원 효율성 : 컨테이너들은 호스트 시스템 지원(CPU, 메모리)을 효율적으로 공유함. 필요한 만큼 사용하고 낭비를 줄임.
  3. 버전 충돌 방지 : 각 컨테이너는 독립적이므로 서로 다른 버전이 충돌없이 실행될 수 있음.
  4. 연결 가능 : 여러 컨테이너가 연결되어 복잡한 서비스를 구성할 수 있음.

 

또한 도커의 가장 큰 장점은 개발 환경에서 서버 환경으로의 배포를 간소화한다는 점.

  1.  선언적 설정 : 개발자는 컨테이너의 설계도(Dockerfile, Docker Compose)를 통해 설치 방법, 실행 방법, 연결 방법들을 명확히 정의할 수 있음 → 환경 일관성 유지
  2. 쉬운 배포 : 코드와 설계도를 서버에 전송하면 동일한 환경이 자동으로 구성됨

 

도커는 서로 다른 기술 스택과 버전들이 충돌 없이 공존할 수 있게 해주며,
개발 환경과 서버 환경의 일관성을 보장함.

 

 

 


 

[실습]

 

docker run -it node

 

로컬 환경에 'node'라는 이미지가 없다면, 자동으로 Docker Hub에서 최신 버전의 Node.js 이미지를 다운로드

이미지 다운로드 후 해당 이미지 기반으로 새로운 컨테이너를 생성하고 실행

 

docker ps

--name 이름 node 이런식으로 이름을 설정하지 않으면 자동으로 컨테이너 이름이 생성됨 (kind_bell)

 

docker exec -it kind_bell bash

'kind_bell' 컨테이너 안에서 bash shell을 실행

컨테이너 내부를 통해 가상의 리눅스 환경으로 들어감

 

 

Dockerfile: 이미지를 만들기 위한 설계도

 

🤔 node 이미지를 받았는데 나만의 이미지를 왜 또 만드냐?

컴퓨터에서 http-server란 프로그램을 돌리려면 Node.js만 설치되어 있어서는 안 됨. npm 등으로 http-server가 전역으로 깔려 있어야 함.
node 이미지는 아직 http-server가 없는 상태이기 때문에, 컨테이너로 실행할 때마다 매번 http-server를 받아줘야 함. (서비스를 돌릴 때마다 이런 모듈을 일일이 다운 받아야 함)
Node.js에다가 http-server까지 깔린 상태가 이미지로 있으면 컨테이너로 서비스를 실행하기 보다 수월해짐~!

 

 

Dockerfile

# 이 node 이미지에 덧붙여서 개조, 튜닝
FROM node:12.18.4

# 이미지 생성 과정에서 실행할 명령어
# Node.js가 깔린 node 이미지여서 npm 명령어 가능
# http-server 설치
RUN npm install -g http-server 

# 이미지 내에서 명령어를 실행할(현 위치로 잡을) 디렉토리 설정
WORKDIR /home/node/app

# 컨테이너 실행시 실행할 명령어
CMD ["http-server", "-p", "8080", "./public"]

# 이미지 생성 명령어 (현 파일과 같은 디렉토리에서)
# docker build -t {이미지명} .

# 컨테이너 생성 & 실행 명령어
# docker run --name {컨테이너명} -v $(pwd):/home/node/app -p 8080:8080 {이미지명}

 

RUN : 이미지를 생성하는 과정에서 실행되는 것, 이미지에서 컨테이너를 실행하는 시점에서는 이미 http-server가 설치.

CMD : 이미지로부터 컨테이너가 만들어져 가동될 때 바로 실행되는 명령어

 

 

1) 이미지 생성

docker build -t frontend-img .

frontend-img 이미지에는 이미 http-server가 깔린 상태

 

2) 컨테이너로 실행 (run)

docker run --name frontend-con -v $(pwd):/home/node/app -p 8080:8080 frontend-img

 

Volume: 컨테이너와 특정 폴더를 공유

 

 

 

FROM mysql:5.7

# 이미지 환경변수들 세팅
# 실전에서는 비밀번호 등을 이곳에 입력하지 말 것!
# 서버의 환경변수 등을 활용하세요.
ENV MYSQL_USER mysql_user
ENV MYSQL_PASSWORD mysql_password
ENV MYSQL_ROOT_PASSWORD mysql_root_password
ENV MYSQL_DATABASE visitlog

# 도커환경에서 컨테이너 생성시 스크립트를 실행하는 폴더로
# 미리 작성된 스크립트들을 이동
COPY ./scripts/ /docker-entrypoint-initdb.d/

 

COPY : RUN처럼 이미지를 생성하는 과정에서 해당 이미지 안에 특정 파일을 미리 넣어둠

'create_table.sql' , 'insert_data.sql' 파일들은 컨테이너 초기화 과정에 필요하기 때문에 COPY로 이미지 안에 미리 넣어두는 것

 

 

 

Docker Compose

여러 컨테이너를 정의하고 실행하기 위한 도구

version: '3'
services:
  database:
    # Dockerfile이 있는 위치
    build: ./database
    # 내부에서 개방할 포트 : 외부에서 접근할 포트
    ports:
      - "3306:3306"
  backend:
    build: ./backend
    # 연결할 외부 디렉토리 : 컨테이너 내 디렉토리
    volumes:
      - ./backend:/usr/src/app
    ports:
      - "5000:5000"
    # 환경변수 설정
    environment: 
      - DBHOST=database
  frontend:
    build: ./frontend
    # 연결할 외부 디렉토리 : 컨테이너 내 디렉토리
    volumes:
      - ./frontend:/home/node/app
    ports:
      - "8080:8080"

 

> 단일 명령어로 모든 컨테이너를 한 번에 빌드, 실행, 관리할 수 있게 해줌

 

 

'DevOps > Docker' 카테고리의 다른 글

Docker 배포 시 아키텍처 불일치  (0) 2025.05.09
Docker Hub에 이미지 올려서 배포해보기  (0) 2025.05.09
Docker 명령어  (0) 2025.05.08

+ Recent posts