지난 시간에는 Dockerfile을 이용해 우리만의 웹 페이지가 담긴 Nginx 이미지를 직접 만들어보았습니다.
이제 우리는 어디서든 동일하게 실행되는 웹 서버 패키지를 갖게 된 셈입니다.
하지만 실제 서비스는 웹 서버 하나만으로 동작하지 않습니다.
보통 웹 서버(프론트엔드), API 서버(백엔드), 데이터베이스 등 여러 컴포넌트가 서로 통신하며 하나의 서비스를 이룹니다.
이번 시간에는 이렇게 여러 개의 컨테이너를 어떻게 연결하고, 또 어떻게 한 번에 쉽게 관리할 수 있는지 알아보겠습니다.
먼저 우리가 만든 이미지를 다른 사람과 공유할 수 있는 Docker Hub에 대해 배우고, 컨테이너들이 서로 대화하는 방법인 네트워크 개념을 살펴봅니다.
마지막으로, 여러 컨테이너를 설계도 한 장으로 관리하게 해주는 강력한 도구,
Docker Hub는 Docker가 공식적으로 운영하는, 전 세계의 Docker 이미지들이 모여있는 거대한 저장소입니다.
마치 스마트폰의 앱스토어처럼, 우리는 이곳에서 필요한 이미지를 검색해서 다운로드(docker pull) 받을 수 있고, 내가 만든 이미지를 업로드(docker push)하여 다른 사람과 공유할 수도 있습니다.
우리가 1편에서 hello-world, 2편에서 nginx 이미지를 별다른 설정 없이 바로 사용할 수 있었던 것도 모두 Docker Hub에 기본적으로 등록된 공식 이미지였기 때문입니다.
https://hub.docker.com/_/hello-world↗https://hub.docker.com/_/nginx↗
Docker Hub - Nginx
비공개로 이미지를 관리하고 싶다면 유료 플랜을 사용하거나, 회사 내부에 별도의 Private Registry를 구축하여 사용할 수도 있습니다.
bridge는 가장 흔하게 사용되는 네트워크 모드입니다.
마치 우리 집에 있는 인터넷 공유기를 상상하면 쉽습니다.
하나의 공유기에 연결된 여러 대의 노트북과 스마트폰은 192.168.0.x 와 같은 내부 IP 주소를 할당받아 서로 자유롭게 통신할 수 있습니다.
하지만 공유기 외부, 즉 인터넷에서는 이 내부 IP 주소를 직접 알 수 없습니다.
Docker의 bridge 네트워크도 마찬가지입니다.
하나의 bridge 네트워크에 연결된 컨테이너들은 각자의 내부 IP와 컨테이너 이름을 가지고 서로 통신할 수 있지만, 외부에서는 직접 접근할 수 없습니다.
외부에서 특정 컨테이너에 접근하려면 2편에서 배운
먼저 API 서버 역할을 할 간단한 Node.js 코드를 준비합니다.
api-server 라는 폴더를 새로 만들고, 그 안에 server.js와 package.json, Dockerfile을 작성합니다.
server.js: /api 경로로 요청이 오면 "Hello from API Server!" 메시지를 응답합니다.
/api-server/server.js
const http = require('http');const server = http.createServer((req, res) => { if (req.url === '/api') { res.writeHead(200, {'Content-Type': 'text/plain'}); res.end('Hello from API Server!'); } else { res.writeHead(404); res.end(); }});server.listen(3000, () => console.log('API Server is running on port 3000'));
이제 Nginx가 /api 요청을 받으면 Node.js API 서버로 전달하도록 설정해야 합니다.
이 역할을
리버스 프록시(Reverse Proxy)
라고 합니다.
2편에서 만들었던 my-nginx-page 폴더에 nginx.conf 파일을 만들고 아래와 같이 작성합니다.
/my-nginx-page/nginx.conf
server { listen 80; location / { root /usr/share/nginx/html; index index.html; } location /api { proxy_pass http://api-server:3000; }}
proxy_pass http://api-server:3000;: /api로 시작하는 모든 요청을 api-server라는 이름을 가진 호스트의 3000번 포트로 전달하라는 의미입니다.
여기서 api-server는 나중에 실행할 Node.js 컨테이너의 이름이 됩니다.
Docker파일을 수정하여 Nginx 이미지도 새로 빌드할 수 있도록 합니다.
/my-nginx-page/Dockerfile
FROM nginxCOPY ./index.html /usr/share/nginx/html/index.html # Nginx 설정 파일을 컨테이너에 복사합니다.COPY ./nginx.conf /etc/nginx/conf.d/default.confRUN chmod 644 /usr/share/nginx/html/index.html
nginx, api-server: 각 서비스(컨테이너)의 이름입니다. 이 이름이 곧 컨테이너의 호스트 이름이 됩니다.
build: Dockerfile이 있는 폴더를 지정하여 이미지를 빌드합니다.
ports: 포트 매핑을 설정합니다.
depends_on: nginx가 api-server에 의존한다는 뜻입니다. Compose는 api-server를 먼저 실행한 뒤 nginx를 실행합니다.
이제 터미널에서 docker-compose.yml 파일이 있는 위치로 이동한 뒤, 아래 명령어 하나만 실행하면 됩니다.
docker-compose up --build
이 명령어는 docker-compose.yml 파일을 읽어 api-server와 nginx 이미지를 빌드하고, 두 개의 컨테이너를 생성하여 실행합니다.
이제 브라우저에서 http://localhost:8080/api 로 접속해보세요.
"Hello from API Server!" 라는 메시지가 보인다면 성공입니다.
api 통신 성공
컨테이너들을 중지하고 싶을 때는 Ctrl + C를 누르거나, 다른 터미널에서 docker-compose down 명령어를 입력하면 됩니다.
오늘은 하나의 서비스를 구성하는 여러 컨테이너를 어떻게 관리하는지 배웠습니다.
Docker Hub를 통해 이미지를 공유하는 방법을 알아보았고, 컨테이너 간의 통신을 가능하게 하는 bridge 네트워크의 개념을 이해했습니다.
무엇보다 가장 중요한 것은,
Docker Compose
라는 강력한 도구를 사용해 Nginx와 Node.js API 서버로 구성된 멀티 컨테이너 애플리케이션을 단 하나의 yml 파일과 명령어로 손쉽게 관리하는 방법을 익힌 것입니다.
이제 우리는 복잡한 애플리케이션도 Docker를 이용해 쉽고 반복 가능하게 배포할 수 있는 능력을 갖추게 되었습니다.
🖐️
하지만 Docker Compose는 여전히 한 대의 컴퓨터(단일 호스트)에서 동작한다는 한계가 있습니다.
만약 우리 서비스가 엄청난 인기를 끌어 수십, 수백 대의 서버로 확장해야 한다면 어떻게 해야 할까요?
이러한 대규모 컨테이너 환경을 관리하기 위한 기술이 바로 다음 시간에 배울