newara-dx: 개발용 컨테이너 만들기
시작하기 앞서, 현재 블로그 주제는 이 주제가 아닌, Kubernetes에 관련된 주제였으나, 몇일 전에 의도치 않게 해당 주제로 많은 시간을 사용해 뉴아라 백엔드 개발용 컨테이너를 완성했기 때문에, 급하게 회고록 형태로 블로그 주제를 바꾸었습니다. 추후 연재? 될 Kubernetes 이야기도 기대해 주세요!
시험 몇일 전에 공부하기 싫어서 작성하는 글이어서, 퀄리티가 약간 떨어질수 있으나, 양해 부탁드립니다.
서론
newara-dx 는 Newara Development Experience 의 약자이다. 노션의 뉴아라의 온보딩 페이지를 보면 알겠지만, 개발 환경 셋팅, 특히 백엔드는 매우 복잡하다. 따라서 이전부터 여유가 되는 SPARCS의 물리 서버를 활용해 개발 컨테이너를 만들자는 이야기가 나왔었다.
최종 목표는 여러 유저들이 독립된 환경에서 빠르게 개발 환경을 갖출 수 있게 하는 것이었다. 완성된 결과는 ‣ 에서 확인할 수 있다.
사실 몇주 전만 해도 개발용 컨테이너를 만들어야겠다는 생각은 전혀 없었었다. 비록 백엔드 온보딩 셋팅이 어렵다는 이야기는 계속해서 나왔었으나, 방학때나 나중에 여유있을때 천천히 만들 생각이었다. 그리고 우리의 NewAra PM님께서도 계속해서 개발용 이미지를 만들고 있다고 해서, 조금씩 도와줄 정도로만 생각했었고, 이렇게 많은 시간을 사용해서 학기 중에 완성하게 될 줄은 상상도 하지 못했다.
컨테이너 제작 시기는 저번주 공동 코딩 시간이었다. 그때 뉴아라 부원분께서 M1 Mac을 쓰시는데, 자꾸 파이썬 환경이 문제를 일으켜 개발을 하지 못한다는 소식을 들었다. 몇주동안 해결을 하는데 어려움을 겪고 있다고 하셔서, PM님께 내가 빠르게 개발용 컨테이너 이미지를 만든다고 했다.
기영 : 또 파이썬이 안돼 인준아
인준 : dockerfile 만들고 컨테이너로 작업하자, 간단해 보이는데 예준이 형이랑 10분안에 해줄래?
이때까지만 해도 나는 한시간, 오래 걸려도 공동 코딩 시간에 끝날 줄 알았다. 왜냐하면 기존에 프론트, 백엔드 CI 를 이전 PM님과 함께 구축을 하기도 했었고, 이번에는 production용이 아닌 develop용이어서 multi-stage build 등등 일부 요소들은 생각해도 안했기 때문에 빨리 끝날 것으로 예상했다.
하지만 거의 5일 꼬박 걸려서 완성했는데, 그 시간동안 왜 이리 오래 걸렸는지, 그리고 어떻게 구현을 해 나갔는지 소개를 하려고 한다.
Dockerfile과 docker-compose.yml, 그리고 docker-entrypoint.sh
처음에는 Dockerfile
과 docker-compose
는 CI용 Dockerfile과 dev 서버에 있는 docker-compose를 거의 그대로 사용해 보려고 했다. 하지만 문제는 production용과 달리 dx는 사용자가 수정한 내용을 저장할 수 있도록 /root
디렉토리와 볼륨 매핑을 해야만 한다. 그렇지만 볼륨 매핑을 하게 되면, 이미지 내 /root
디렉토리의 파일들은 전부 사라져 버리는 문제가 발생한다!
따라서, 약간의 꼼수가 필요한데, 이미지 단에서 생성된 파일은 /tmp
폴더에 저장하고, 컨테이너가 실행 될 시 이 폴더로부터 복사해 원하는 디렉토리에 붙여넣는 것이다. 이를 위해서 Dockerfile의 entrypoint로 /docker-entrypoint.sh
를 실행하게 해, 해당 쉘 스크립트에서 /tmp
폴더에서 우리가 원하는 폴더로 복사토록 했다.
매핑된 볼륨이 비어있을 때만 초기화(initialize) 스크립트가 실행하게 해야 되므로, if ! [ "$(ls -A /root/api/apps)" ]; then
와 같은 조건문을 사용했다. 해당 디렉토리가 비어있는 경우, if 문 안을 실행한다.
여기서 잠깐, Dockerfile
에서 RUN
과 ENTRYPOINT
의 차이를 알아야한다. CMD
도 있으나, ENTRYPOINT
와 거의 유사하다.
RUN
: 이미지를 제작하는 과정에서 실행되는 명령어,RUN apt-get update
등 이미지를 구성하는데 필요한 환경 설정을 하는데 사용한다.ENTRYPOINT
: 이미지가 실행되어 컨테이너로 되었을 때, 실행할 명령어. 컨테이너에서 1번 프로세스로 실행된다. 만약 컨테이너를 올렸을 때 바로 React 앱을 띄워지게 하고 싶으면ENTRYPOINT ["npm", "run", "serve"]
의 형태로 사용하면 된다. 그리고 개발용 컨테이너를 만들고 싶으면ENTRYPOINT ["sleep", "infinity"]
를 하면 된다. 1번 프로세스가 죽지 않게 만들어야 하기 때문이다. bash를 띄워도 되지만, interactive + tty 옵션을 주어야 하는 번거로움이 있다.
ENTRYPOINT
로 실행해야할 명령어가 많으면, 따로 docker-entrypoint.sh
로 분리를 한 뒤, 이미지 빌드시 COPY
를 통해서 이 스크립트를 /
디렉토리로 복사한 뒤, ENTRYPOINT ["bash", "-c", "/docker-entrypoint.sh"]
로 실행하는 것이 좋다.
Files
실행하는데 반드시 필요한 .env
파일을 포함해 아래와 같이 구성했다.
api/
Dockerfile
.env
docker-entrypoint.sh
web/
Dockerfile
.env
docker-entrypoint.sh
: 최종적으로npm run serve
실행 - 백엔드 개발자가 프론트 컨테이너를 건드리지 않아도 됨
docker-compose.yml
: api
web
mysql
redis
elasticsearch
NOTE
Dockerfile
의 경우 명령어의 순서가 중요하다. CACHING되는 수준이 다르기 때문이다.- CI용 이미지를 그대로 쓰니 몇몇 python 관련 문제가 나타났다. 어찌저찌해서 잘 해결했고, virtualenv를 쓰지 말자는 얘기도 나왔지만 그냥 poetry의
virtualenv