이번에는 간단한 웹 애플리케이션을 docker를 이용해 구동해보겠습니다.
실습 예제 제공 사이트에서 한 예제를 그대로 따라할 것입니다.
실습 예제 깃허브에 들어가서 실습할 파일들을 가져올 수 있습니다.
참고로 5챕터의 Demo Application Overview를 하고 있습니다.
먼저 데이터베이스로 PostgreSQL을 사용할 것입니다. 그렇기 때문에 PostgreSQL이미지를 가져옵시다.
태그로 15.1-alpine을 사용할 것입니다.
docker pull postgres:15.1-alpine

이제 가져온 이미지로 컨테이너를 만들어 봅시다.
그 전에 Docker Hub에 들어가서 postgres를 실행시키기 위해 설정한 옵션들을 알아봅시다.
아래와 같이 공식 사이트에 보면 어떻게 실행해야 할지가 적혀 있습니다.
아래는 postgres를 실행시키기 위한 최소 조건임을 알아야 합니다.

우리는 영구적인 데이터 저장을 위해 volume도 달아줄 것입니다.
또한 외부에서 접근하게 하기 위해 로컬 포트와 연결도 시켜줄 것입니다.
문제는 volume을 어디에 마운트해야 모르는데 이것도 공식 문서에서 찾을 수 있습니다.
대충 volume을 검색하면 아래와 같이 볼륨을 마운트하는 예제를 보여줍니다.

그리고 port를 매핑하기위해 postgres의 기본 포트 번호를 알아야 하는데 이는 postgres의 공식문서에 5432포트라고 적혀있습니다.

이제 postgres에 사용할 볼륨을 만듭시다.
docker volume create 명령어를 이용하면 됩니다.
docker volume create postgres-volume

이제 이 볼륨을 postgres 컨테이너를 실행하면서 마운트 합시다.
docker run -d --name postgres-db -e POSTGRES_PASSWORD=test1234@ -v postgres-volume:/var/lib/postgresql/data -p 8001:5432 postgres:15.1-alpine
위 명령에서 -d는 백그라운드로 실행하겠다는 것입니다.
-p에서 8001:5432라고 하였는데 이는 로컬 포트 8001을 컨테이너 포트 5432와 연결하겠다는 것입니다.
저는 로컬에 Postgresql이 설치되어있어 8001에 연결하였습니다.
이제 컨테이너가 실행되었는지 확인해 봅시다.
docker container ls

이제 node를 통해 서버를 열고 해당 서버를 postgres 데이터베이스가 있는 컨테이너와 연결하겠습니다.
먼저 node 서버를 구성하는 파일들을 살펴봅시다.
먼저 src/db.js 파일을 살펴보겠습니다.
// src/db
const fs = require('fs');
const { Pool } = require('pg');
databaseUrl =
process.env.DATABASE_URL ||
fs.readFileSync(process.env.DATABASE_URL_FILE, 'utf8');
const pool = new Pool({
connectionString: databaseUrl,
});
// the pool will emit an error on behalf of any idle clients
// it contains if a backend error or network partition happens
pool.on('error', (err, client) => {
console.error('Unexpected error on idle client', err);
process.exit(-1);
});
// async/await - check out a client
const getDateTime = async () => {
const client = await pool.connect();
try {
const res = await client.query('SELECT NOW() as now;');
return res.rows[0];
} catch (err) {
console.log(err.stack);
} finally {
client.release();
}
};
module.exports = { getDateTime };
db파일에는 postgres 데이터베이스와 연결합니다.
그리고 연결된 상태에서 에러에 대해 Unexpected error on idle client를 출력하게 만들었습니다.
또한 getDateTime함수를 통해 데이터베이스에 연결한 후 현재 시간을 구하는 쿼리를 요청하고 현재 시간을 응답으로 받아 가져옵니다.
이번에는 src/index.js 파일을 보겠습니다.
const { getDateTime } = require('./db');
const express = require('express');
const morgan = require('morgan');
const app = express();
const port = process.env.PORT || 3000;
// setup the logger
app.use(morgan('tiny'));
app.get('/', async (req, res) => {
const dateTime = await getDateTime();
const response = dateTime;
response.api = 'node';
res.send(response);
});
app.get('/ping', async (_, res) => {
res.send('pong');
});
const server = app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
process.on('SIGTERM', () => {
console.debug('SIGTERM signal received: closing HTTP server');
server.close(() => {
console.debug('HTTP server closed');
});
});
index.js파일에는 /경로로 데이터베이스에 현재 시간을 쿼리한 결과를 응답으로 보내는 로직과 /ping 경로로 pong을 응답으로 보내는 로직이 존재합니다.
또한 process.on('SIGTERM', ...) 을 통해 'SIGTERM'이라는 종료 신호를 보내면 서버가 종료되게 만들고 있습니다.
docker로 node서버를 실행해 봅시다.
이를 위해 먼저 node 이미지를 가져옵시다.
docker pull node:latest
이후 dockerfile을 생성합시다.
# 베이스 이미지로 Node.js 최신 버전을 사용합니다.
FROM node:latest
# 앱 디렉토리를 생성하고, 그 디렉토리를 워킹 디렉토리로 설정합니다.
WORKDIR /usr/src/app
# package.json과 package-lock.json을 워킹 디렉토리로 복사합니다.
COPY package*.json ./
# 의존성을 설치합니다.
RUN npm install
# 앱 소스 코드를 복사합니다.
COPY . .
# 애플리케이션이 3000 포트를 사용한다고 가정하고 이 포트를 엽니다.
EXPOSE 3000
# 앱을 실행합니다.
CMD ["npm", "start"]
.dockerignore파일을 만들어 이미지 생성시 무시할 파일들을 등록해 둡시다.
node_modules
.npm
참고로 node 서버의 파일 구조는 다음 사진과 같습니다.

이제 dockerfile을 이용해 이미지를 만듭시다.
docker build -t my-node-app .
이제 컨테이너들을 위한 네트워크를 만들어야 합니다.
컨테이너의 네트워크를 만드는 이유는 컨테이너 입장에서는 localhost가 자신 컨테이너이기 때문입니다.
즉, 각 컨테이너가 자신만의 localhost를 가지기 때문에 localhost:3000과 같은 방식으로 서로 연결할 수가 없습니다.
그래서 docker를 이용해 컨테이너끼리 네트워크 통신을 할 수 있는 네트워크를 만드는 것입니다.
docker network create명령어를 통해 network를 만들 수 있습니다.
docker network create my-network
이제 해당 네트워크에 기존에 만들었던 PostgreSQL 컨테이너를 넣어봅시다.
docker network connect 명령어로 가능합니다.
docker network connect my-network postgres-db
네트워크에 정상적으로 연결되었는지 확인하고 싶다면 docker network inspect를 이용하면 됩니다.
docker network inspect 92edb976db05
주의할 점은 네트워크 ID를 통해 inspect해야 한다는 것입니다.
이제 node 서버를 컨테이너로 실행해 봅시다.
그전에 염두해야할 점은 -e 옵션을 통해 process.env.DATABASE_URL를 전달해야 한다는 것입니다.
또한 호스트는 같은 네트워크에 있는 컨테이너의 이름으로 설정해야 합니다.
DATABASE_URL형식은 다음과 같아야 합니다.
postgres://사용자이름:비밀번호@호스트:포트/데이터베이스이름
만약 제가 한 방식을 그대로 따라했다면 다음과 같게 됩니다.
postgres://postgres:test1234%40@postgres-db:8001/postgres
이제 node 앱 이미지를 컨테이너로 만들어봅시다.
docker run -d -p 3000:3000 -e DATABASE_URL=postgres://postgres:test1234%40@postgres-db:5432/postgres --network my-network --name node-app my-node-app
위에서 설명할 것은 --network 옵션인데 이는 컨테이너를 실행하면서 특정 네트워크에 연결하게 해주는 것이니다.
이렇게 서버도 실행했다면 http://localhost:3000 으로 접속해 봅시다.

위와 같이 떴다면 문제 없이 작동한 것입니다.