⌨️ Docker
도커에 대해 공부하고 이전에 팀원이 배포한 환경에서 몇 가지를 고려하여 변경하여 적용하였다. 그러면서 있었던 문제들에 대해 기록해두고자 한다.
1. Docker-compose
우선 첫 번째는 Docker-compose 파일이다.
1.1. Docker Life Cycle 관리
우리 서비스는 그래도 Spring Cloud Gateway와 Feign Client를 사용한 MSA 아키텍처로 설계했다. 우선 기술적 난도와 활용도에 대해서는 차치하고
위 방식에서 Docker-compose로 실행을 하려니 이런 문제점이 발생했다.
- Eureka 서버가 가장 먼저 켜져야한다.
- Config와 Gateway는 크게 개의치 않으나 Eureka 다음에 켜지는 것이 좋다.
- MySQL과 Redis 서버는 API와 Batch 서버가 켜지기 이전에 켜져야 한다.
크게 보면 두가지의 흐름이 생성된다.
그림으로 나타내자면 이런 구조인데, 나는 Config서버는 제외하고 각자 yaml파일을 작성해 주는 것으로 했다.
이걸 토대로 docker-compose.yaml 파일을 작성한다면,
- Gateway는 Eureka에 depends on 하고 있다.
- API, Batch는 Eureka, MySQL에 depends on 하고 있다.
로 작성하면 될 것이다.
1.2. 수정 전 compose와 dockerfile
version: '3'
services:
eureka:
container_name: tiary-eureka
image: ckaanf/tiary-eureka:tiary
ports:
- "8761:8761"
restart: always
server:
container_name: tiary-server
image: ckaanf/tiary-server:tiary
ports:
- "8088:8088"
depends_on:
- eureka
restart: unless-stopped
batch:
container_name: tiary-batch
image: ckaanf/tiary-batch:tiary
ports:
- "8089:8089"
depends_on:
- eureka
restart: unless-stopped
gateway:
container_name: tiary-gateway
image: ckaanf/tiary-gateway:tiary
depends_on:
- eureka
redis:
container_name: tiary-redis
image: redis
ports:
- "6379:6379"
restart: unless-stopped
전문은 아니지만 위에 말한 것과 같은 부분들만 작성했다.
이때는 MySQL을 AWS 라이트세일의 MySQL서버를 사용하여 3306 포트의 문제가 없었다. 이것은 아래 항목에서 다루도록 하겠다.
아래는 Server의 Dockerfile이다
FROM openjdk:17-jdk
ARG JAR_FILE=build/libs/tiary-0.0.1-SNAPSHOT.jar
COPY ${JAR_FILE} app.jar
#ENTRYPOINT ["java","-jar","/app.jar"]
CMD ["sh", "-c", "sleep 20 && java -jar /app.jar"]
CMD를 보면 sleep 20을 준 것을 알 수있다.
내가 Docker 파일을 수정한 이유가 된 부분이기도 하다.
🌟 CMD의 Sleep은 정적으로 작동한다.
이 표현이 맞나..? 모호하지만 더 좋은 표현을 찾아서 고치도록 하겠다.
우선은 어떤 의미로 말한거냐면, 앞의 Eureka와 관계없이 Server가 Running이 들어간다면 정해진 시간만큼 sleep 했다가 다음 행을 실행한다는 것이다.
즉 내가 정해준 시간 20초 동안 선행되어야할 서버가 올라가지 않으면 문제는 똑같이 발생한다.
이는 depends_on을 해두어도 완벽히 순서가 보장되지는 않기 때문에 발생한 것인데 sleep으로 해결을 하셨던 것 같다.
나는 이부분을 다르게 해결하려 한다.
1.3. 수정 후 compose와 dockerfile
version: '3'
services:
eureka:
container_name: tiary-eureka
image: ckaanf/tiary-eureka:tiary
ports:
- "8761:8761"
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://tiary-eureka:8761/actuator/health"]
interval: 10s
timeout: 5s
retries: 3
build:
context: ./eureka # Eureka 서비스의 Dockerfile이 있는 경로
server:
container_name: tiary-server
image: ckaanf/tiary-server:tiary
ports:
- "8088:8088"
depends_on:
- eureka
- mysql
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://tiary-server:8088/actuator/health"]
interval: 10s
timeout: 5s
retries: 3
build:
context: ./server # Server 서비스의 Dockerfile이 있는 경로
batch:
container_name: tiary-batch
image: ckaanf/tiary-batch:tiary
ports:
- "8089:8089"
depends_on:
- eureka
- mysql
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://tiary-batch:8089/actuator/health"]
interval: 10s
timeout: 5s
retries: 3
build:
context: ./batch # Batch 서비스의 Dockerfile이 있는 경로
gateway:
container_name: tiary-gateway
image: ckaanf/tiary-gateway:tiary
ports:
- "8090:8090"
depends_on:
- eureka
healthcheck:
test: ["CMD", "curl", "-f", "http://tiary-gateway:8090/actuator/health"]
interval: 10s
timeout: 5s
retries: 3
build:
context: ./gateway # Gateway 서비스의 Dockerfile이 있는 경로
redis:
container_name: tiary-redis
image: redis
ports:
- "6379:6379"
restart: unless-stopped
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 3
mysql:
container_name: tiary-mysql
image: mysql:latest
environment:
MYSQL_ROOT_PASSWORD:
MYSQL_DATABASE:
MYSQL_USER:
MYSQL_PASSWORD:
ports:
- "3307:3306"
restart: unless-stopped
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "tiary-mysql", "-u", " ID ", "-p"]
interval: 10s
timeout: 5s
retries: 3
# command: --init-file /docker-entrypoint-initdb.d/init.sql
# volumes:
# - ./mysql:/docker-entrypoint-initdb.d
나는 healthcheck 를 통해 helathy 할 경우 다음 서버가 가동되게 compose를 설정해 주었다. 그리고 dockerfile은 좀 더 유연히 관리하기 위해 docker profile의 yaml을 실행하는 것으로 바꾸었다.
FROM openjdk:17-jdk
ARG JAR_FILE=build/libs/tiary-3.0.0.jar
COPY ${JAR_FILE} app.jar
CMD ["java", "-jar", "-Dspring.profiles.active=docker", "/app.jar"]
이렇게 함으로써 정적으로 sleep을 통해 관리하기보다 좀 더 Docker에 의해 제대로 관리되는 Life Cycle을 적용하였고,
이후 서버의 기동 순서로 인한 문제는 없었다.
그러나 MySQL을 로컬 환경 - Docker를 쓰든 호스트 머신을 쓰든 클라우드 환경이 아닌 경우- 에서 사용하려니 많이 헤맸는데,
이 부분은 사실 Docker라기보다 네트워킹이 관련이 있긴 하지만 2항에서 다루도록 하겠다.
2. 문제 상황
2.1. MySQL 컨테이너와의 연결
문제 상황은 MySQL 컨테이너와의 연결에서 있었다. 우선 그 전에 짚고 가야 할 것이 있다.
- docker-compose로 한번에 실행시킨 네트워크는 한 영역에 묶인다.
- MySQL을 실행하는 compose 파일에 작성한 user는 기본적으로 읽기 쓰기 권한을 가진 채로 만들어진다.
아마 compose 파일을 유심히 읽으신 분은 아시겠지만, MySQL의 포트 매핑이 3307로 되어있다.
그렇다, 나는 호스트 머신에도 MySQL이 깔려 있기 때문에 3306 포트를 사용할 수가 없었다.
네트워크에 대해 잘 모르는 상태로 어떤 참고자료만 보고 따라하다가는 이런 상황에서 원치 않는 오류가 발생하고 원인이 무엇인 지 찾을 때 오래 걸릴 수 있다.
호스트 머신을 기본 바탕으로 올라가는 Docker 환경은 포트 매핑을 통해 localhost로도 접속이 가능하다,
그러나 일반적으로 클라우드 환경까지 고려하여 처음부터 컨테이너명으로 연결을 하는 것이 좋다.
예를 들어 mysql://localhost:3307 보다 tiary-mysql:3307로 하는 것이 말이다.
그럼 위에 말한 걸 포트매핑을 3306으로 바꿔서 생각해보자
localhost:3306 / tiary-mysql:3306 -> 즉 아무리 컨테이너명으로 해줘도 호스트 머신에 MySQL이 깔려있다면 3306 포트는 호스트 머신에 MySQL인지 Docker에 MySQL인지 판단을 하기가 어렵다는 것이다.
물론 컨테이너 명을 명시하여 컨테이너의 MySQL로 연결을 하도록 할 수 있지만,
아마 Host에서 서버를 기동하고 Docker를 켜보시면 알겠지만, 포트는 공유한다.
VMware처럼 가상의 OS를 설치하는 것이 아니기 때문에 3306을 같이 쓰면서 컨테이너 명으로 구분할 수가 없다는 것이다.
그래서 결국 3307 포트로 바꿔주었다.
생각해 보면 별 것 아닌 문제였는데 Docker에 대한 숙련도 부족으로 판단하여 꽤 오래 고민했는데..
네트워킹의 문제였다..
🌟 MySQL이 호스트 머신에 깔려있다면 서로 매핑 포트를 3306 말고 다른 것으로 해주자