글을 시작하기 전...
결론적으로 이 글은 Spring 프로젝트를 Dockerfile 로 빌드한 후 Github Actions 로 Docker Hub 에 올리고 EC2 환경에서 각종 컨테이너들을 Docker-Compose 로 관리하는 것을 목표로 이것 저것 해보면서 발생했던 모든 문제들을 다루는 글이라 내용이 다소 난잡하고 중구난방일 수 있다.
프로젝트 환경
진행했던 프로젝트가 있는데 성공적으로 끝마치고 나니 프로젝트 때 맡은 부분을 구현하느라 미처 해보지 못했던 서버 구성을 해보고 싶었고 이것 저것 실험해보고 싶은게 생겨 개인 서버를 구축해 프로젝트를 그대로 실행시켜 보기로 했다.
구 프로젝트는
1. Github Actions 로 jar 파일을 직접 배포하여 실행
2. MySQL 은 RDS 를 구성하여 사용
3. Redis 는 별도의 Redis Cloud 로 구성해서 사용
4. Docker-Compose 로 nginx 와 app 을 관리하였다.
당시 내가 서버 관리자가 아니였기 때문에 프로젝트 ec2 에 접속해서 이런 저런 파일들을 다 살펴보고 삽질하며 파악한 결과로 전 팀원의 의도랑은 조금 다를 수 있다..!
나는 AWS 프리티어의 노예이므로 1년이 지났을 때를 대비한 서버 구성 및 데이터 백업을 하여 다른 EC2 머신에 최대한 간결하고 빠르게 이전하는 것이 최대 목표이다. (물론 한 서비스를 1년 동안 유지시켜본 적은 없다)
어느 정도 규모가 있는 프로젝트의 서버를 구성해보고 이를 최대한 간편하게 백업/복구 하는 쪽으로 연습을 하게 되었다.
그래서 생각한 것은
1. 모든 것을 Docker 로 관리한다. (Spring 프로젝트, MySQL, Redis 등등, Github Actions 로 jar 를 배포하는 식으로 하면 deploy 결과에 따라 nohub.out 파일이 제대로 생기지 않는 등 로그를 확인하는 것이 불편한 문제를 해결)
2. DB 데이터를 주기적으로 백업시킨다.
3. Shell Script 를 최대한 활용한다.
이다.
제일 처음한건 프로젝트를 그대로 fork 해와서 서버 관련 부분을 내 EC2 에 맞춰서 재배포를 하는 것이었다.
위 과정에서 전 프로젝트에서 DB로 RDS 를 쓰고 있는 상황에서
옮긴 EC2 서버에서 Docker 컨테이너로 돌아가는 MySQL 로 대치를 성공했다.
각종 오류들은 여기서부터 발생한다.
문제 상황
0. Spring 프로젝트 내에서 Docker-Compose 사용하면서...
처음에는 Dockerfile 로 Spring 프로젝트를 빌드하고
(spring 프로젝트 내에) docker-compose.yml 로 MySQL 과 Spring 을 구성하였고
Github Actions 에서 deploy.yml 로 image 들을 Docker Hub 에 올리고 pull 받아 실행하는 흐름으로 진행해봤었는데 매끄럽게 잘 되지 않고 Docker 로 돌아갈 것이 MySQL, Spring 외에도 더 있기 때문에 위 방식은 포기하고 Docker Hub 에 이미지를 업로드까지만 하고 EC2 내에 docker-compose 를 구성하여 모든 컨테이너를 docker-compose up / down 으로 한 번에 관리하는 것으로 변경
1. MySQL 데이터 백업을 위해 Docker Volume 를 사용하면서...
우선 프로젝트를 진행할 때의 데이터를 내 MySQL 컨테이너에 복원을 하였고 데이터가 들어있는 컨테이너를 Docker Hub 에 백업하고자 했다. (이때까지만 해도 컨테이너 자체를 이미지화하여 Docker Hub 에 올리면 안에 들어있는 데이터까지 올라가는 줄 알았다.)
https://gukjan9.tistory.com/117
아무리 이미지를 올리고 다운 받아도 데이터는 존재하지 않아서 구글링을 하다보니
Docker 를 통해 실행되어도 데이터는 로컬에 저장된다는 것을 알았고
이 같은 상황을 위해 Docker Volume 이 존재하는 것을 알게 되었다.
당시 띄워둔 MySQL 컨테이너를 처음 실행할 때 volume 옵션을 주지 않았기 때문에 우선 어딘가에 저장된 데이터를 새로 생성한 볼륨에 옮기는 작업을 하고자 했다.
docker run -it --rm -v mysql-container-data:/from -v newdata:/to busybox sh -c "cp -r /from/* /to/"
docker run -d --name new-mysql-container -v newdata:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw mysql:latest
위 과정을 따라하여 data-reuse 라는 볼륨을 생성하였다.
(컨테이너를 생성할 때 --rm 옵션을 넣고 실행해야 컨테이너가 삭제될 때 생성된 볼륨도 같이 삭제가 되는데 안해서 생긴 것 같다..)
그리고 docker-compose.yml 에 컨테이너가 실행될 때 data-reuse 볼륨을 참조하도록 해놓았다.
version: "3.1"
services:
mysql:
image: gukjang/mysql-reuse:1.0.0
container_name: mysql-reuse-test
env_file:
- .env
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_DATABASE_USERNAME}
MYSQL_PASSWORD: ${MYSQL_DATABASE_PASSWORD}
ports:
- 3306:3306
networks:
- network-reuse
restart: always
volumes:
- data-reuse:/var/lib/mysql
spring:
image: gukjang/spring-reuse:latest
container_name: spring-reuse
ports:
- 8080:8080
networks:
- network-reuse
restart: always
depends_on:
- mysql
networks:
network-reuse:
driver: bridge
volumes:
data-reuse:
external: true
이렇게 하니 뜨는 오류
com.mysql.jdbc.exceptions.jdbc4.CommunicationsException: Communications link failure
Spring 컨테이너 로그에선 정확한 이유가 나오진 않고 그냥 DB 연결이 안된다는 오류를 뱉어내고
2024-01-08 09:48:15+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.2.0-1.el8 started.
2024-01-08 09:48:16+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
2024-01-08 09:48:16+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.2.0-1.el8 started.
'/var/lib/mysql/mysql.sock' -> '/var/run/mysqld/mysqld.sock'
2024-01-08T09:48:16.892748Z 0 [System] [MY-015015] [Server] MySQL Server - start.
2024-01-08T09:48:17.391400Z 0 [Warning] [MY-011068] [Server] The syntax '--skip-host-cache' is deprecated and will be removed in a future release. Please use SET GLOBAL host_cache_size=0 instead.
2024-01-08T09:48:17.397599Z 0 [System] [MY-010116] [Server] /usr/sbin/mysqld (mysqld 8.2.0) starting as process 1
2024-01-08T09:48:17.416582Z 1 [ERROR] [MY-011011] [Server] Failed to find valid data directory.
2024-01-08T09:48:17.416862Z 0 [ERROR] [MY-010020] [Server] Data Dictionary initialization failed.
2024-01-08T09:48:17.416966Z 0 [ERROR] [MY-010119] [Server] Aborting
2024-01-08T09:48:17.417510Z 0 [System] [MY-010910] [Server] /usr/sbin/mysqld: Shutdown complete (mysqld 8.2.0) MySQL Community Server - GPL.
2024-01-08T09:48:17.418770Z 0 [System] [MY-015016] [Server] MySQL Server - end.
docker-compose 에서 restart 옵션을 주어서 그런지 무한 restart 가 되면서
로그는 데이터 경로를 찾을 수 없다고 뜬다.
GPT 가 알려준대로 도커 볼륨을 새로 생성하고 거기에 백업을 해놨는데 안된다니...
docker inspect 도커볼륨이름
sudo ls -a /var/lib/docker/volumes/data-reuse/_data/mysql
docker inspect 로 제대로 생성이 된 것을 확인,
직접 구성요소들을 조회하면서 데이터가 있는 것까지 확인했다.
하지만 아무리 구글링을 하여 뭔가를 해봐도 오류가 사라지지 않았다.
해결 (임시)
일단 생성한 data-reuse 를 포기하고
처음에 생성했던 데이터 복원에 성공한 MySQL 컨테이너를 다시 실행하여 이 컨테이너의 데이터가 마운트된 경로를 살펴봤다.
docker inspect 02dc (해당 컨테이너 ID 앞 4자리)
a0ef 로 시작되는 경로를 참조하고 있는 것을 확인하였고
mysql:
image: gukjang/mysql-reuse:1.0.0
container_name: mysql-reuse-test
env_file:
- .env
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_DATABASE_USERNAME}
MYSQL_PASSWORD: ${MYSQL_DATABASE_PASSWORD}
ports:
- 3306:3306
networks:
- network-reuse
restart: always
volumes:
- /var/lib/docker/volumes/a0ef7be124a92a0d57e8ec13c245bd32fc0959970edb115b819add2d3f949a9b/_data/:/var/lib/mysql
volumes 에 경로를 절대경로로 고정하니
MySQL 컨테이너에서는 더이상 오류를 뿜어내지 않고 시간이 지나도 재실행되지 않는다.
하지만 Spring 쪽은 아직 똑같은 오류를 뿜어내고 있었다.
근본적으로 해결된건 아니지만 블로그 포스팅을 마치고 Docker Volume 쪽을 더 살펴볼 것이다.
2. Docker Networks 를 사용하면서...
도커 컨테이너 간에 통신을 위해서는 Bridge 네트워크를 구성해야 해서
docker network create --driver bridge [옵션] 네트워크_이름
명령어로 브릿지 네트워크를 구성해주었다.
docker network ls
docker network ls 로 생성된 네트워크를 확인할 수 있다.
네트워크를 생성해주고
docker-compose 에는
version: "3.1"
services:
mysql:
image: gukjang/mysql-reuse:1.0.0
container_name: mysql-reuse-test
env_file:
- .env
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_DATABASE_USERNAME}
MYSQL_PASSWORD: ${MYSQL_DATABASE_PASSWORD}
ports:
- 3306:3306
networks:
- network-reuse
restart: always
volumes:
- /var/lib/docker/volumes/a0ef7be124a92a0d57e8ec13c245bd32fc0959970edb115b819add2d3f949a9b/_data/:/var/lib/mysql
spring:
image: gukjang/spring-reuse:latest
container_name: spring-reuse
ports:
- 8080:8080
networks:
- network-reuse
restart: always
depends_on:
- mysql
networks:
network-reuse:
driver: bridge
volumes:
data-reuse:
external: true
각 컨테이너에 사용할 networks 를 명시해주고
아래에도 networks 를 따로 명시해주었다.
이렇게 하니
docker-compose up 이 실행될 때마다
새로운 브릿지 네트워크가 생성되었고 (***_network-reuse 의 *** 는 ubuntu 이다)
docker inspect 네트워크_이름
새로 생성된 네트워크를 보면 컨테이너들이 붙어있는 것을 확인할 수 있다.
docker-compose 에 명시한 네트워크 와는 다른 네트워크를 사용하고 있어서 spring 에서 db 를 연결할 수 없다고 뜨는건가 싶어서 수정해보았다.
해결
volume 처럼 직접 생성한 것은 external 옵션을 주어야해서
networks:
network-reuse:
external: true
driver: bridge
위처럼 수정하고 docker-compose up 을 해주었는데
ERROR: Network network-reuse declared as external but specifies additional attributes (driver).
같은 에러가 뜨면서 실행이 되지 않았다.
내용을 보면 external 이라고 선언이 되어 있는데 다른 추가 속성이 있다고 하는 것 같아
networks:
network-reuse:
external: true
bridge 속성을 제거해주었더니
docker-compose up 을 해도 새로운 네트워크가 생성되지 않으며
docker-compose down 을 해도
라며 내가 생성한 네트워크는 삭제되지 않는 것을 확인할 수 있었다.
docker inspect 상으로도 내가 생성한 네트워크에 두 컨테이너가 잘 붙어있는 것을 확인하였지만 여전히 spring 은 db 랑 연결이 안된다고 뜬다.
3. docker-compose 구성시 environment 를 추가하면서...
3번은 온전히 내 실수로 인해 헤맸던 문제이다.
우선 mysql 에서만 environment 를 구성해주었고 (MYSQL_DATABASE, MYSQL_USER, MYSQL_PASSWORD 등등)
spring 에서는 그냥 networks 나 ports, depends on 같은 것만 설정해주었다.
하지만 여러 docker-compose 파일들을 보면서 뭔가 잘못됐음을 느꼈고 뒤늦게 추가하였다.
version: "3.1"
services:
mysql:
image: gukjang/mysql-reuse:1.0.0
container_name: mysql-reuse-test
env_file:
- .env
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_DATABASE_USERNAME}
MYSQL_PASSWORD: ${MYSQL_DATABASE_PASSWORD}
ports:
- 3306:3306
networks:
- network-reuse
restart: always
volumes:
- /var/lib/docker/volumes/a0ef7be124a92a0d57e8ec13c245bd32fc0959970edb115b819add2d3f949a9b/_data/:/var/lib/mysql
spring:
image: gukjang/spring-reuse:latest
container_name: spring-reuse
environment:
SPRING_DATASOURCE_URL: ${MYSQL_DATABASE_URL}
SPRING_DATASOURCE_USERNAME: ${MYSQL_DATABASE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${MYSQL_DATABASE_PASSWORD}
ports:
- 8080:8080
networks:
- network-reuse
restart: always
depends_on:
- mysql
networks:
network-reuse:
external: true
volumes:
data-reuse:
external: true
이렇게 해주어도 결국 똑같은 오류가 뜨긴 했지만 고쳐야할 부분 중 하나였다.
MySQL 부분과 Spring 에서 입력해주어야할 environment 가 살짝 헷갈릴 수 있는데
<MySQL>
MYSQL_DATABASE: ${MYSQL_DATABASE} - 데이터베이스 이름 그 자체 ex) test
MYSQL_USER: ${MYSQL_DATABASE_USERNAME} - 데이터베이스 유저 이름
MYSQL_PASSWORD: ${MYSQL_DATABASE_PASSWORD} - 데이터베이스 유저 비밀번호
<Spring>
SPRING_DATASOURCE_URL: ${MYSQL_DATABASE_URL} - 데이터베이스 URL ex) jdbc:mysql://mysql-test:3306
SPRING_DATASOURCE_USERNAME: ${MYSQL_DATABASE_USERNAME} - 데이터베이스 유저 이름
SPRING_DATASOURCE_PASSWORD: ${MYSQL_DATABASE_PASSWORD} - 데이터베이스 유저 비밀번호
로 작성해주면 되는데 한 가지 주의해야할 점은
MySQL 컨테이너를 단독으로 사용할 때는 jdbc:mysql://localhost:3306 라고 localhost 로 적어주면 되지만
네트워크 상으로 연결된 상태로 사용할 때는 jdbc:mysql://mysql-test:3306 처럼 localhost 대신 해당 컨테이너 이름으로 적어주어야 한다. (이 부분도 꽤나 삽질을 했다...)
4. jdbc SSL , allowPublicKeyRetrieval 문제...
0 ~ 3번까지의 과정을 하나 하나 해결하였지만 여전히 Spring 에서는 communication link failure 가 떴다.
해당 오류를 그대로 복사해서 구글링을 하니 link failure 되는 이유는 또 여러 가지라 갖가지 해결 방법들이 나오는데
이것 저것 해보다가 나는 이 블로그에 나와있는 해결 방법으로 해결했다.
https://blog.naver.com/PostView.naver?blogId=kkson50&logNo=222942723322
SSL 이 뭔진 모르겠고...
SPRING_DATASOURCE_URL 에서
jdbc:mysql://컨테이너_이름:3306/데이터베이스_이름?useSSL=false
뒤에 데이터베이스_이름?useSSL=false 를 입력해주니
드디어 오류 메세지가 바뀌었다...
Public Key Retrieval is not allowed
구글링 해보니
아까 입력해주었던 useSSL=false 뒤에 &allowPublicKeyRetrieval=true 를 추가해주면 해결이 된다한다.
jdbc:mysql://컨테이너_이름:3306/데이터베이스_이름?useSSL=false&allowPublicKeyRetrieval=true
여기까지 추가하고 기계적으로 docker-compose up 을 해주며 또 무슨 오류가 기다리고 있을까 하는 마음에 docker logs 를 계속 살펴보았는데
이 상태로 더이상 로그가 올라오지 않았고
??? 인 상태로 웹에 접속을 해보았는데
복원한 데이터베이스가 적용된 상태로 웹페이지가 떴고 모든 기능이 정상적으로 작동하였다...
한 3, 4일을 여기까지 해내려고 하나 하나 오류를 수정해왔었는데 정말 이게 끝날 줄은 몰랐다...
아직도 붙일게 많이 남아있지만 (redis, nginx 등등)
위 과정을 거치면서 대략적인 틀을 구성하였고 방법과 흐름을 알았으니 나머지 작업들은 나름 수월하게 하지 않을까 싶은 생각이 드는데 크나큰 착각이겠지...