본문 바로가기

프로젝트 일지

#18. Docker 적용하기

#. Docker 사용해보기.

[개발했던 도서 관리 API 프로젝트에 Docker를 적용해보자]

[개요]

Docker는 Application에 포함된 라이브러리들을 독립된 개체인 이미지로 변경하고,
이미지들을 기반으로 컨테이너를 생성하여 Application과 관련된 라이브러리를
독립적인 실행 환경을 갖은채로 관리하는 플랫폼이다.

libraryAPI의 예제를 사용할 경우,
Docker에서 컨테이너화된 Java Application컨테이너화 된 MySQL이 서로 통신을 해야한다.

 

[큰 흐름]

1. MySQL 이미지 생성 -> 컨테이너 생성

2. LibraryAPI 이미지 생성 -> 컨테이너 생성

3. LibraryAPI 컨테이너와 MySQL 컨테이너 통신하여 서버 가동

4. Docker Hub Repository 업로드하기

 

#1. [Docker MySQL 이미지 다운 + 컨테이너 생성]

<A. Docker Hub에서 공식 MySQL 이미지를 받는 방법>

1. Docker Hub에서 MySQL Docker 이미지 다운하여 로컬 이미지 저장소에 저장.

docker pull mysql

2. 다운로드한 Docker 이미지 확인

docker images

 

<B. 내가 설정한 방식대로 MySQL 이미지를 생성하는 법>

주로 초기 데이터를 넣고 싶을 때 이렇게 직접 MySQL 이미지를 생성한다.

1. Dockerfile을 생성

# 공식 mysql을 초기 이미지로 셋팅.
FROM mysql:latest

# 초기 SQL 데이터파일을 docker entrypoint 로 복사.
COPY book.sql /docker-entrypoint-initdb.d/
COPY library.sql /docker-entrypoint-initdb.d/
COPY library_book.sql /docker-entrypoint-initdb.d/

#환경변수 MYSQL_DATABASE을 설정
ENV MYSQL_DATABASE library_db

FROM mysql:latest

가장 최근 버젼의 mysql의 이미지를 기반으로 mysql 이미지를 생성하겠다.

 

COPY book.sql /docker-entrypoint-initdb.d/
이미지를 생성할 때 book.sql 파일을 MySQL /docker-entrypoint-initdb.d/ 경로로 복사하겠다.
**설명**
MySQL 컨테이너는 이미지가 시작될 때
/docker-entrypoint-initdb.d/ 디렉토리를 자동으로 확인하여 SQL 스크립트 파일을 실행한다.
즉 COPY키워드로 특정 파일을 기본 시작 스캔 위치로 복사하고,
컨테이너가 실행될 때 해당 파일들을 시작하도록 하는 역할.

 

ENV MYSQL_DATABASE library_db
환경변수 MYSQL_DATABASE 에 library_db 지정.

2. 작성한 Dockerfile을 토대로 MySQL 이미지를 생성.

docker build -t {원하는 이미지 이름} .

 

3. 생성한 이미지를 기반으로 컨테이너 생성

docker run --name {컨테이너_이름} -e MYSQL_ROOT_PASSWORD=pw1234 -e MYSQL_DATABASE=library_db -d -p 3305:3306 {대상_이미지_이름}

 

--name : 생성할 컨테이너 이름
-e 환경변수 설정
MYSQL_ROOT_PASSWORD=<password> : 본인 MySQL 패스워드로 대체
MYSQL_DATABASE=library_db : library_db 데이터베이스가 이미지 생성 시점에 생성됨.
-d: 백그라운드에서 실행
-p : Docker가 실행되고 있는 기계의 포트 : Docker MySQL 컨테이너 내부 포트
-p [Docker 호스트의 포트] : [MySQL 컨테이너의 포트]
만약, 로컬에 3306 포트번호로 MySQL 이용중이면 도커 MySQL은 3305와 같이 다르게 입력해준다.
마지막은 image 이름.


4. MySQL 컨테이너 생성 후 MySQL Workbench에서 연결시키기

Hostname : Docker가 실행되고 있는 호스트(Docker 실행중인 PC)의 IP 주소(로컬이면 localhost)
Port : Docker 호스트의 바인딩된 포트번호 (3305)

 

#2. [Java Application Docker 이미지 빌드하기]

본격적인 애플리케이션을 컨테이너화 하기 전, 하기 3가지 과정이 진행되야한다.

A. application.yml 수정
MySQL을 로컬에서 사용하는게 아니라 Docker에 생성된 MySQL 컨테이너와 연결되도록 설정.

B. Dockerfile 생성 (최상위 디렉토리에 생성)
애플리케이션을 Docker 이미지로 생성하기 위한 스크립트.
빌드 결과인 jar 파일을 새 Docker 이미지안에 복사하는 작업을 수행하고,
8080 포트를 열어 애플리케이션을 실행하도록 작성.

 

Optional docker-compose.yml

더보기

C. docker-compose.yml 생성 (최상위 디렉토리에 생성)
여러 Docker 컨테이너를 함께 관리하기 위한 설정 파일.
MySQL의 Docker 컨테이너 : mysql-containerlibraryAPI의 컨테이너 : libraryAPI 를 대상으로 정의. 

[A. application.yml 수정]

로컬 MySQL이 아닌 Docker에 생성된 MySQL 컨테이너와 연결되도록 변경.

spring:
  datasource:
    url: jdbc:mysql://mysql:3306/library_db
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: pw1234
  jpa:
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
    hibernate:
#      ddl-auto: create
      ddl-auto: update
#      ddl-auto: none
    show-sql: true
    properties:
      hibernate:
        format_sql: true
  sql:
    init:
      mode: never
#      mode: always
#      mode: never
      encoding: UTF-8
      separator: ";"
      data-locations: classpath:/db/*

logging:
  level:
    org:
      springframework:
        orm:
          jpa: DEBUG
mail:
  address:
    admin: admin@gmail.com

jwt:
  key: ${JWT_SECRET_KEY}
  access-token-expiration-minutes: 30
  refresh-token-expiration-minutes: 420

jdbc:mysql://<hostname>:<port>/<database>

<hostname>

DB 서버의 호스트 이름이나 IP 주소 입력.
컨테이너 사용할 때는 컨테이너 이름 입력

<port>

MySQL 서버가 수신 대기하고 있는 포트 번호를 의미.
Docker에서 MySQL 컨테이너를 생성할 때, -p <호스트 포트> : <컨테이너 포트> 형식을 사용하는데,
여기서 우측 <컨테이너 포트>가 url의 포트 자리에 위치해야 한다.

jdbc:mysql://mysql:3306/library_db

여기서는 mysql은 MySQL 서버가 실행되는 컨테이너 이름.
3306은 MySQL 서버가 수신 대기하고 있는 포트 번호.
이 url은 다른 컨테이너에서 이 MySQL 서버에 연결할 때 사용된다.

<database>

연결할 DB의 이름.

[B. Dockerfile 생성]

자바 애플리케이션을 Docker 이미지로 생성하는 스크립트

#1. 원하는 애플리케이션의 최상위 루트에 Dockerfile을 생성한다.

그럼 위의 사진과 같이 Docker plugin 설치 안내가 나오고 OK를 누른다.


#2. 생성한 Dockerfile의 내용을 작성.

Dockerfile은 Java application을 이미지화 하는데 사용되는 설정 내용이다.

FROM openjdk:11-jdk as build
WORKDIR /workspace/app

COPY . /workspace/app

RUN ./gradlew build -x test

# 실행 단계
FROM openjdk:11-jre-slim

# 빌드 단계에서 생성된 jar 파일을 현재 위치(/app)로 복사
COPY --from=build /workspace/app/build/libs/*.jar app.jar

EXPOSE 8080

ENTRYPOINT ["java","-jar","app.jar"]

FROM openjdk:11-jdk as build
Docker 이미지를 만들기 위해 'openjdk:11-jdk' 라는 기본 이미지를 사용하겠다.
as build : 이 단계를 build라는 이름으로 참조하겠다.

WORKDIR /workspace/app
Docker 컨테이너 안에서 작업 디렉토리를 '/workspace/app'로 설정.
이 후 명령어 들은 이 디렉토리를 기준으로 실행.

COPY . /workspace/app
현재 호스트(Docker가 실행중인 PC)의
모든 파일과 디렉토리(.)를 컨테이너의 '/workspace/app' 디렉토리로 복사하겠다.

RUN ./gradlew build -x test
./gradlew build -x test 를 실행하여 애플리케이션을 빌드한다.
-x test 는 테스트를 실행하지 않고, 빌드만 진행하라는 의미.

FROM openjdk:11-jre-slim
두 번째 단계를 시작하며, 'openjdk:11-jre-slim' 이라는 기본 이미지를 사용하겠다.

jre-slim 
Java Runtime Environment가 포함된 상대적으로 작은 크기의 이미지를 의미.

COPY --from=build /workspace/app/build/libs/*.jar app.jar
build라는 이름의 이전 단계에서 생성된 jar 파일을 현재 위치(/app)로 복사.

EXPOSE 8080
컨테이너의 8080포트를 외부에 노출시킨다.

ENTRYPOINT ["java","-jar", "app.jar"] 
컨테이너가 시작될 때마다 실행될 명령어를 설정. java -jar app.jar 명령어가 실행되어 Java 앱을 실행.

이렇게 Dockerfile은 각 단계별로 명령어를 정의하며, 그 결과를 Docker 이미지로 만든다.
이 Docker 이미지는 컨테이너를 생성하고 실행하는데 사용된다.

#3. Docker Desktop을 실행 후, 터미널 상에서 Dockerfile과 같은 디렉토리로 이동.

애플리케이션을 빌드하여 이미지화 하는 과정

docker build -t {원하는_이미지_명} .

-t libraryapi .
자바 애플리케이션을 이미지화하고 -t 뒤에는 애플리케이션의 이미지 이름을 지정.


#4. 이미지를 실행하여 컨테이너 생성.

$ docker run --network={생성한_네트워크_이름} -d --name {생성하려는_컨테이너명} -d -p 8080:8080 {앱_이미지명}

코드에 네트워크를 넣은 이유는 아래 링크를 통해 에러 해결에 의한 설정임을 참고하세요.

https://dvdhan.tistory.com/246

 

#10. Docker - java.net.UnknownHostException: mysql

Docker로 자바 애플리케이션, MySQL의 이미지를 생성했고, 각 이미지를 실행시켜 컨테이너를 생성. 하지만 자바 애플리케이션의 컨테이너 실행 시, MySQL의 컨테이너를 찾지 못해 컨테이너의 실행 X.

dvdhan.tistory.com

 

Optional docker-compose.yml 예시

더보기

[C. docker-compose.yml 생성]

여러 개의 Docker 컨테이너를 정의하고 실행하기 위한 도구.
여러 개의 컨테이너의 설정을 단일 파일로 관리하고,
이 파일에 따라 컨테이너를 동시에 생성, 시작, 중지 가능하다.
이는 복잡한 애플리케이션을 여러 서비스로 분리하고 이들을 동시에 관리하도록 해주고,
마이크로서비스 아키텍처를 쉽게 구성하고 관리할 수 있게 한다.

version: '3'
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    image: library
    ports:
      # Docker를 실행하고 있는 PC의 포트 : Docker 컨테이너 내부의 포트
      # 즉 외부에서 이 앱 8080으로 보내는 요청은 우항의 Docker컨테이너로 연결.
      - "8080:8080"
      # app은 해당하는 서비스 이름에 의존하고 있다는 뜻.
      # 아래에 mysql-container 라는 서비스로 컨테이너를 칭하고 있다.
    depends_on:
      - mysql-container
    # Spring Boot 애플리케이션에서 MySQL 컨테이너에 접근하기 위한 설정.
    environment:
      - SPRING_DATASOURCE_URL=jdbc:mysql://mysql-container:3306/library_db
      - SPRING_DATASOURCE_USERNAME=root
      - SPRING_DATASOURCE_PASSWORD=pw1234
      - JWT_SECRET_KEY=davidhanisnothungryrightnow

    # Docker에 생성된 MySQL 컨테이너의 설정
  mysql-container:
    image: mysql:5.7
    restart: always
    environment:
      # MySQL 컨테이너 전체 권한을 가진 root 비밀번호
      MYSQL_ROOT_PASSWORD: pw1234
      # MySQL 컨테이너에서 생성할 DB의 이름
      MYSQL_DATABASE: library_db
      # MySQL 컨테이너에서 생성할 DB에 대한 권한을 가진 계정,비밀번호
#      MYSQL_USER: root
#      MYSQL_PASSWORD: pw1234
    volumes:
      - db-data:/var/lib/mysql
    expose:
      - "3306"

volumes:
  db-data:

version : 3
이 필드는 Docker Compose 파일의 버전을 나타낸다. 3이 현시점 가장 최신 버젼.


service
이 섹션 아래에는 실행하려는 각 컨테이너를 정의한다.app과 mysql-container 라는 2개의 서비스가 정의.

app
app 서비스에 대한 설정.

build
이미지를 빌드하는 방법이 정의.

build.context
Dockerfile이 위치한 디렉토리

build.dockerfile
앱을 Docker 이미지로 빌드할 설정이 담긴 Dockerfile의 이름.

image
생성될 이미지의 이름을 설정.



port - "A:B"
A : Docker를 실행하고 있는 호스트 포트 번호
B : Docker 컨테이너 내부의 포트 번호
즉, 외부에서 이 앱의 8080으로 보내는 요청은 Docker 컨테이너로 연결된다.

depends_on
이 서비스가 다른 서비스에 의존한다는 것을 의미. 즉 Docker에 있는 MySQL 컨테이너에 의존성이 있다.

environment
Spring Boot 애플리케이션(app 컨테이너)에서 Docker의 MySQL 컨테이너에 접근하기 위한 설정.

mysql-container
2번째 서비스인 Docker에 생성된 MySQL 컨테이너의 설정.
이 MySQL 컨테이너는 mysql:5.7 이미지를 기반으로 생성.

restart: always
컨테이너가 실패한 경우, 항상 재시작.

environment
MySQL 서버의 설정을 정의한다.

#A. MYSQL_ROOT_PASSWORD
MySQL 컨테이너 전체 권한을 가진 root 비밀번호

#B. MYSQL_DATABASE
MySQL 컨테이너에서 생성할 DB의 이름. (MySQL workbench의 Connection)

#C. MYSQL_USER, MYSQL_PASSWORD
MySQL 컨테이너에서 생성할 DB에 대한 권한을 가진 사용자 계정, 비밀번호 = B에 대한 사용자 계정

volumes: - db-data: /var/lib/mysql
Docker 컨테이너 내부의 MySQL 서버가 데이터를 저장하는 기본 위치.
Docker의 볼륨을 이 위치에 연결하면 MySQL 서버에 의해 생성되거나 변경된 데이터가
컨테이너 외부에 보존되어 컨테이너를 재시작하거나 삭제해도 데이터가 유지된다.

volumes: db-data:
docker-compose.yml 파일에서 정의된 모든 볼륨을 나열한다.
이 예제에서는 db-data 라는 볼륨이 정의되어 있다.

이는 mysql-container 컨테이너에 연결되어 MySQL 데이터베이스의 데이터를 보존한다.

 

#3. [컨테이너 생성]

docker-compose up
docker-compose down
docker volume prune

docker-compose up : 컨테이너 생성
docker-compose down : 실행중인 컨테이너 종료
docker volume prune : 볼륨 삭제
=> Docker compose는 이전에 생성된 볼륨을 재사용한다.
만약 이전에 오류가 발생했던 데이터가 있다면 그대로 사용하게 된다.
이를 위해 docker-compose down으로 종료하고 나면 docker volume prune으로 삭제하는 습관을 들이자.

이렇게 MySQL Docker 이미지 생성 -> MySQL 컨테이너 생성,
자바 애플리케이션 이미지 생성 -> 자바 애플리케이션 컨테이너 생성을 완료하면
Docker Desktop 에는 아래 사진과 같이 나와야 한다.

Images
Containers

Containers 들이 모두 정상적으로 동작 중임을 알 수 있다.

 

 

#3. 자바 애플리케이션, MySQL 컨테이너 실행하여 서버 구동하기

MySQL Workbench에서 데이터들이 제대로 들어왔는지 확인해보자.

포트 3305로 연결된 Connections 클릭!

자바 애플리케이션의 컨테이너가 MySQL 컨테이너과 정상 연결되어 모든 테이블이 생성되었다!

마지막으로 애플리케이션에 작성해둔 메서드를 Postman을 이용해 요청을 보내보면

이렇게 정상 작동을 한다 !!

이로써 Library_API 프로젝트를 도커 컨테이너 적용을 마쳤다.

 

#4. Docker Hub Repository 업로드

Docker Hub의 Repository에 이미지 업로드는 다음과 같은 3단계의 과정을 갖는다.

아래 코드는 Docker가 설치된 시스템의 어느 위치에서나 가능함. 즉 터미널의 모든 위치에서 다 사용 가능.

A. Docker 이미지를 Docker Hub에 맞게 태그 지정하기

docker tag {로컬_이미지_이름}:{로컬_이미지_태그명} {DockerHub_계정}/{Repository_이름}:{원하는_업로드_이미지_태그}

 

B. Docker Hub에 로그인

docker login

C. Docker 이미지를 Docker Hub에 푸쉬하기

docker push {DockerHub_계정명}/{repository_이름}:{원하는_업로드_이미지_태그명}

D. Docker 이미지를 다운 받을 때

docker pull {DockerHub_계정명}/{repository_이름}:{원하는_이미지_태그}

이미지 이름은 필요 없다.