이번 프로젝트를 진행하면서 이전에 작성한 포스팅의 내용인 웹소켓 통신과 대용량 응답 데이터를 처리하는 다양한 방법 중 하나를 적용해보는 작업과 함께 새로 처음해보면서 알게되었던 기술인 aws cli 사용과 docker-compose 적용을 어떻게 했는지 적어보려한다.
웹소켓도 웹소켓이지만 이번 글은 더욱 나중의 나에게 도움이 될 것 같기에 작성하기로 하였다.
먼저, 이전글과 마찬가지로 aws가 뭐고~ docker가 뭐고~ docker-compose가 뭐고~ 이러한 내용은 작성하지 않으려고 한다. 다른 블로그에 개념에 대한 정리가 엄청나게 잘되어 있기 때문에 순수하게 기술에 대한 정의를 알고 싶다면 유용하지 않을 것이다.
이전의 프로젝트에서는 이미 작성된 Dockerfile과 docker-compose, nginx 파일을 그대로 긁어와서 사용해도 문제가 없었지만, 이번 시작하게 된 프로젝트는 처음부터 시작해야됐기 때문에 꽤나 복잡한 부분이 많았다.
시작은 aws cli설치부터 시작했다. 설치는 각 운영체제에 맞게 설치하면된다. 공식 홈페이지가 있기 때문에 설치는 문제가 없었다.
그다음은 cli에서 사용할 aws 계정에 로그인 하는것이였다. 회사에서는 모든 계정을 모아둔 1password를 사용하고 있었기 때문에 aws계정 IAM의 엑세스키와 시크릿키를 사용해서 로그인을 하면 됐다.
aws configure를 입력하면 자동으로 다음 단계로 넘어가니까 key를 치고있지 말자
aws configure
AWS Access Key ID [None] : [IAM의 Access Key ID]
AWS Secret Access Key [None] : [IAM의 Secret Access Key]
Default region name [None] : ap-northeast-2
Default output format [None] : text / json / table
다 입력한 후에 aws sts get-caller-identity 를 입력해서 내가 로그인한 aws정보가 잘 나오면
aws cli를 쓸 준비가 된 것이다.
그 다음에 해야할 것은 백엔드쪽에서 ecr에 푸쉬해둔 리포지토리를 pull을 통해 가져오는 것이다.
- aws 콘솔에 접속해서 ecr탭에 들어간다.
- 내가 pull하고자 하는 리포지토리를 누른다.
- 우측 상단에 푸쉬 명령 보기 라는 버튼을 누른다.
- 제일 첫번째 명령어( aws ecr get-login-password ~샬라샬라 )를 복사해서 aws cli를 작성했던 커맨드창에 복사해서 실행시킨다.
현재 aws cli에 로그인되어있는 유저가 인증된 유저인지 확인하는 작업이다. - aws ecr describe-repositories를 입력했을 때 해당 리포지토리가 나오면 4번의 작업이 정상적으로 완료된 것이다.
- docker pull [[AWS 계정 ID 12자리]] .dkr.ecr.ap-northeast-2.amazonaws.com/리포지토리이름:버전
위의 명령어를 입력해주면 되는데 AWS 계정 ID는 푸쉬 명령 보기 버튼을 눌러보면 명령어들 사이에 껴있는 숫자 12자리가 있을 것이다. 그것이 고유 AWS 계정 ID이다. - docker run -d [[AWS 계정 ID 12자리]] .dkr.ecr.ap-northeast-2.amazonaws.com/리포지토리이름:버전
이제 위의 명령어를 작성하면 도커 컨테이너를 실행하게 된다. - docker ps 를 입력해서 현재 원하는 도커 컨테이너가 잘 실행되고 있는지 확인한다.
위의 과정이 완료되면 도커 컨테이너를 실행시켜서 도커 이미지를 사용할 준비가 끝난 것이다.
이제 로컬에서 도커 이미지를 띄우고 실행시켜보자
docker와 docker-compose를 설치완료했다는 가정하에 방법을 적을 것이다.
나는 Dockerfile, docker-componse.yml을 작성해주었다.
Dockerfile
Dockerfile은 완성된 이미지를 생성하기 위해 컨테이너에 설치해야하는 패키지, 추가해야하는 소스코드,
실행해야 하는 명령어 등등을 입력해두면 이 파일을 읽어서 도커 이미지로 만들어주는데 해당 과정을 기록한 파일의 이름을 Dockerfile이라고 한다.
즉, Dockerfile에 작성된 코드들에 따라서 이미지를 생성한다고 생각하면 될 것 같다.
나는 내가 작성한 Dockerfile만 설명하고, 나머지 명령어들은 https://docs.docker.com/reference/dockerfile/
Dockerfile reference
Find all the available commands you can use in a Dockerfile and learn how to use them, including COPY, ARG, ENTRYPOINT, and more.
docs.docker.com
위의 사이트에 자세히 나와있으니 살펴보면 좋을 것 같다!
내가 작성해둔 명령어는
- FROM
- WORKDIR
- COPY
- RUN
- EXPOSE
- CMD
이렇게 6가지를 작성해두었다.
간단하게만 설명해두면
FROM : 반드시 들어가야하는 값이고, 생성할 이미지의 베이스가 될 이미지를 결정하는 명령어이다.
WORKDIR : 명령어를 실행할 디렉토리를 지정하는 명령어이다. 쉽게말해서 작성한 경로 하위파일들이 생성되는 것이다.
나는 nextjs app router를 사용하고 있어 /app 으로 지정해주었다.
COPY : 현재 디렉토리의 모든 파일과 디렉토리를 작성한 디렉토리에 모두 복사하는 명령어이다.
나는 package.json ./ 라고 작성했는데 package로 시작하고, .json으로 끝나는 파일이나 디렉토리를 전부 ./ 경로의 디렉토리에 전부 복사하는 것이다.
RUN : 이미지를 만들기 위해 컨테이너 내부에서 실행할 명령어이다.
나는 npm ci로 작성했는데, npm install이 아닌 npm ci로 해둔 이유는 package-lock.json을 변경하지 않고 package-lock.json에 맞는 종속성을 버전에 맞게 설치해주기 때문에 빌드를 안정적으로 할 수 있기 때문이다.
EXPOSE : 나는 로컬에서 3001 포트를 사용하려고 했기에 3001이라고 적었다.
CMD : 해당 이미지로 컨테이너를 실행할 때 어떤 명령어를 사용할 건지를 결정하는 명령어이다.
나는 ["npm", "run", "dev"] 로 작성을 해주어서 npm run dev 명령어를 실행시켜 주었다.
docker-compose
도커 컴포즈는 여러 컨테이너를 하나의 서버에서 하나의 서비스로 정의해서 컨테이너 묶음으로 관리할 수 있게끔 해주는 관리 도구이다.
우리가 서비스를 구동하기 위해 생성해야하는 컨테이너는 한두개가 아니게 될 수 있다. 그럴 때 컨테이너를 하나 하나씩 생성해야하는데, 이걸 하나로 묶어서 관리할 수 있게끔 하는 도구라고 생각하면 될 것이다.
현재 컨테이너로 관리하고 있는 것은 웹 애플리케이션, 웹서버, redis, mongo 등 db 컨테이너들이 있다.
이 모든 컨테이너를 하나하나 생성하고 관리하기엔 더 늘어날 수 있는 옵션들이 많아질 수 있기에 도커 컴포즈를 통해 하나로 묶어서 순차적으로 생성할 수 있게끔 해주는 것이다.
백엔드쪽에서 만들어준 웹서버, db에 관한 docker-compose 코드는 만들어주신 코드들을 그대로 받았다.
나는 프론트엔드쪽의 컨테이너만 만들어주면 됐다.
하지만 도커에 무지했던 나는 받은 docker-compose 코드만 가지고 docker를 실행시켰고, 왜... 웹 애플리케이션은 실행되지 않는걸까.. 하는 괴상한 생각에 빠졌다. 열심히 구글링을 시작했다. 찾아간 블로그들의 코드를 비교하기 시작했다. 그러다 나는 웹 애플리케이션에 대한 컨테이너 코드가 없으니까 실행이 안된다는 당연한 생각을 하게 되었다.
조금 많이 어이가 없었긴 했지만 이제라도 알았으니 다행이다. 하마터면 백엔드 찾아갈뻔했다 라고 생각했다.
그럼 내가 작성한 웹 애플리케이션 서비스 설정 코드를 간단한 설명과 함께 적어보겠다
- restart: always
컨테이너가 비정상적으로 종료될 경우 재시작되도록 설정 - command: npm run dev
컨테이너가 시작되면 npm run dev 명령어를 실행시켜 개발 서버를 시작하도록 설정 - container_name: frontend
컨테이너 이름을 frontend로 지정 - working_dir: /app
컨테이너 내에서 작업 디렉토리를 /app 으로 설정 - volumes: .:/app
현재 디렉토리(.)을 컨테이너의 /app 디렉토리에 마운트하여,
코드 변경 사항이 컨테이너에 실시간으로 반영될 수 있도록 설정 - build: context: .
Docker 빌드의 컨텍스트 디렉토리를 현재 디렉토리(.)로 지정 - build: dockerfile: Dockerfile
현재 디렉토리에서 Dockerfile을 사용해 이미지를 빌드 - environment: WATCHPACK_POLLING=true
파일 변경을 감지하는 기능을 활성화하여 개발 환경에서 파일 변경시 자동으로 갱신되도록 설정 - environment: NODE_ENV=development
환경 변수를 설정하여 Node.js 애플리케이션이 개발 모드로 실행되도록 설정 - ports: '3001:3001'
호스트 포트인 3001을 컨테이너의 포트 3001에 매핑해서 localhost:3001로 접근하여 접속하게 설정 - stdin_open: true
터미널 입력을 허용하여, 컨테이너에서 상호작용할 수 있도록 설정
위처럼 docker-compose 파일을 작성해주었는데, environment 부분은 처음에 없었다.
이대로 docker를 실행시켜보니까 3001포트로 웹 애플리케이션에 잘 접속하는걸 볼 수 있었다.
하지만 문제는 로컬에서 코드 수정 후에 개발환경에서 바로 hot reload가 안되어
수정된 부분이 개발 환경에서 자동으로 바뀌지 않았다. 이것도 마찬가지로 폭풍 구글링..
해결방법은 위와 같은 옵션을 쥐어주는 거였다. docker 컨테이너는 개발환경이 어떻게 수정되고 있는지는 전혀 중요하지 않았다. 그저 이미지가 빌드되는대로 띄워주면 되는거였다. 따라서 파일 변경 옵션을 따로 주어 hot reload가 안되는 문제를 해결할 수 있었다.
.dockerignore
마지막으로 dockerignore 파일이다.
이 파일은 빌드 컨텍스트에 필요 없는 파일이 포함되는걸 막기 위해서 존재하는 파일이다.
그냥 쉽게말해, 이 파일안에 작성한 이름을 가진 파일은 도커가 빌드될 때 제외가 된다는 것이다.
.gitignore와 비슷한 느낌인 것 같다.
나는 해당 파일에
- .idea
- .git
- .gitignore
- .dockerignore
- Dockerfile
- *.md
- *.sh
- *.yml
- **/node_modules
위의 이름들을 작성해주었다.
다른 것들은 분명히 도커 빌드시 필요없어도 된다고 생각을 했는데,
Dockerfile이 필요가 없을까? 생각을 해보았다.
dockerignore를 구글에 검색해 가면서 다른사람들은 어떤 파일들을 작성했고, 왜 작성했는지를 알아보았다.
나는 docker-compose.yml에서 각 컨테이너마다 만들어진 이미지를 참조해서 사용되기 때문에
Dockerfile없이 docker-compose.yml만을 사용해서 애플리케이션을 구성할 수 있다는 답을 얻을 수 있었다.
지금까지가 aws cli 설치부터 docker-compose.yml 작성 후 도커 실행까지의 과정이였다.
텍스트로만은 차마 다 담지 못한 희노애락이 있었지만, 프론트엔드가 최소한으로 가져야할 도커 설정에 대한 지식을 이번기회를 통해 많이 얻게 된 것 같아 분명히 매우 소득이 있는 과정이였다. 다음 도커설정을 해야할 일이 생기면 이번보다는 덜 헤맬 것이라고 생각한다!
이번 프로젝트가 nginx를 사용하게 될 수도 있기 때문에
지금 작성해둔 코드에서 nginx가 도입되면서 생기는 과정도 시간내서 작성할 생각이다.