간단한 Node.js 서버를 활용하여 Docker Container의 성능을 벤치마킹하는 프로젝트입니다. 네트워크, CPU, 디스크 I/O 성능을 측정하여 네이티브 환경과 도커 컨테이너 환경의 성능 차이를 비교/분석하고, 최적의 배포 환경을 알아보고자 합니다.
// app.js
app.get('/network', (req, res) => {
res.send('Network endpoint response');
});
app.get('/disk-io', (req, res) => {
const filePath = 'testfile.txt';
const fileContent = 'Some content to write to the file';
fs.writeFile(filePath, fileContent, (err) => {
if (err) {
res.status(500).send('Error writing file');
return;
}
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
res.status(500).send('Error reading file');
return;
}
res.send(`File content: ${data}`);
});
});
});
app.get('/cpu', (req, res) => {
const start = Date.now();
for (let i = 0; i < 1e5; i++) {
crypto.createHash('sha256').update('test').digest('hex');
}
const end = Date.now();
res.send(`CPU intensive task completed in ${end - start} ms`);
});
/network
: 네트워크 성능 측정. 다른 영향을 최소화하도록 간단한 응답을 반환합니다./disk-io
: 디스크 I/O 성능 측정. 파일을 생성하고 읽어서 응답을 반환합니다./cpu
: CPU 성능 측정. CPU 집약적인 해싱 작업을 반복하여 응답을 반환합니다.동일한 Node.js 코드를 각각 Native, Docker container(bridge, host)에서 실행
wrk
(벤치마킹 툴)를 활용한 아래의 스크립트로 각 엔드포인트에 대한 요청을 1분간 수행
#!/bin/bash
DURATION="1m"
THREADS=32
CONNECTIONS=400
wrk -t$THREADS -c$CONNECTIONS -d$DURATION http://0.0.0.0:3000/network > ./host/host_network.txt
wrk -t$THREADS -c$CONNECTIONS -d$DURATION http://0.0.0.0:3000/disk-io > ./host/host_disk_io.txt
DURATION="1m"
THREADS=2
CONNECTIONS=4
wrk -t$THREADS -c$CONNECTIONS -d$DURATION http://0.0.0.0:3000/cpu > ./host/host_cpu.txt
Native
node app.js # 3000번 포트로 서버 실행
Docker container(bridge)
docker run \
--rm \
-v /home/ubuntu:/usr/src/app \
-w /usr/src/app \
-p 3000:3000 # 포트 매핑, 브릿지 네트워크 사용 \
node:18 \
node app.js
Docker container(host)
docker run \
--rm \
-v /home/ubuntu:/usr/src/app \
-w /usr/src/app \
--network host # 호스트 네트워크 사용 \
node:18 \
node app.js
Native
≈ Docker container(host)
> Docker container(bridge)
Native
> Docker container(host)
≈ Docker container(bridge)
Native
> Docker container(host)
≈ Docker container(bridge)
네트워크 성능 (Requests/sec)
테스트 | 네이티브 | Docker (bridge) | Docker (host) |
---|---|---|---|
1회차 | 5731.09 | 7008.73 | 7718.26 |
2회차 | 5785.42 | 6923.70 | 7485.36 |
3회차 | 5559.80 | 6961.60 | 7580.54 |
평균 | 5692.10 | 6964.68 | 7594.72 |
CPU 성능 (Requests/sec)
테스트 | 네이티브 | Docker (bridge) | Docker (host) |
---|---|---|---|
1회차 | 3.51 | 4.23 | 4.21 |
2회차 | 3.46 | 4.16 | 4.16 |
3회차 | 3.51 | 4.16 | 4.24 |
평균 | 3.49 | 4.18 | 4.20 |
디스크 I/O 성능 (Requests/sec)
테스트 | 네이티브 | Docker (bridge) | Docker (host) |
---|---|---|---|
1회차 | 4326.90 | 3965.71 | 4291.90 |
2회차 | 4434.90 | 3971.72 | 4234.98 |
3회차 | 4262.51 | 3926.30 | 4349.31 |
평균 | 4341.44 | 3954.58 | 4292.06 |
단순 요약
Docker container(host)
> Docker container(bridge)
> Native
Docker container(bridge)
≈ Docker container(host)
> Native
Native
> Docker container(host)
> Docker container(bridge)
Latency
RPS
네트워크 성능 측면에서 Docker container(host)
설정이 가장 우수한 성능을 보였습니다. 이는 아무래도 Docker container(bridge)
와 비교했을 때, 호스트 네트워크 인터페이스를 직접 사용하는 host
모드의 이점 때문으로 보입니다.
Docker container(host)
: 호스트의 네트워크 인터페이스를 공유하여 네트워크 오버헤드가 거의 없습니다.Docker container(bridge)
: 네트워크 패킷이 브리지 네트워크를 통해 라우팅되는 과정을 거치며 미세한 오버헤드가 발생합니다. 미미하다고 볼 수도 있지만, 네트워크 성능이 중요한 서비스라면 고려해야 할 수준으로 보입니다.Native
: 정말 모르겠습니다. 다만, 아마도 Docker container(host)
에서 사용한 Node:18 공식 이미지에 뭔가 최적화 튜닝이 되어있지 않았을까 하는 생각이 듭니다.CPU 성능은 의외로 Native
에서 가장 좋지 않았습니다. 이는 아무래도 Node:18 공식 이미지의 최적화와 관련이 있어 보입니다.
Docker container
: Node:18 이미지 내의 최적화와 컨테이너 환경의 경량화가 CPU 성능 에 유리한 효과를 준 것 같습니다. 다만 확인이 필요합니다.Native
: 가장 우수할것이라는 예상과는 반대로, 가장 떨어지는 성능을 보였습니다. 정직하게 정석대로 설치된 Ubuntu 24.04 환경이 원인일 수도 있겠습니다.디스크 I/O 성능에서는 Native
환경이 미세하게 더 나은 성능을 보였습니다. 아무래도 Native
환경이 파일 시스템에 직접 접근할 수 있다는 게 큰 이유로 보입니다.
Native
: 파일 시스템에 직접 접근하므로 가장 우수한 성능을 보인 것 같습니다.Docker container(host)
: Native
와 거의 유사한 성능을 보였습니다. 네트워크 인터페이스 뿐 아닌 파일 시스템에도 직접 접근할 수 있는것인지 확인해 보아야겠습니다.Docker container(bridge)
: 브릿지 모드와 호스트 모드는 네트워크에만 있는 것으로 알고있었는데, 디스크 I/O 성능에서도 꽤나 큰 차이를 보였습니다. 원인은 아직 모르겠습니다.Docker 컨테이너의 호스트 모드와 브리지 모드는 주로 네트워크 인터페이스에 영향을 미치는 설정입니다. 그러나 디스크 I/O 성능에서도 차이가 나는 이유는 몇 가지가 있을 수 있습니다.
Docker는 컨테이너의 파일 시스템을 관리하기 위해 다양한 스토리지 드라이버를 사용합니다. 가장 일반적인 드라이버는 overlay2입니다. 스토리지 드라이버는 컨테이너의 파일 읽기 및 쓰기 작업의 성능에 큰 영향을 미칩니다.
호스트 네트워크 모드와 브리지 네트워크 모드에서 스토리지 드라이버의 동작이 약간 다를 수 있으며, 이로 인해 디스크 I/O 성능에도 차이가 발생할 수 있습니다. 예를 들어, 호스트 모드에서 네트워크 관련 오버헤드가 줄어들면서 디스크 I/O 작업에 더 많은 자원이 할당될 수 있습니다.
브리지 네트워크 모드는 네트워크 패킷을 브리지 네트워크를 통해 라우팅해야 하므로 추가적인 네트워크 오버헤드가 발생합니다. 이 오버헤드는 네트워크 트래픽이 많은 경우 디스크 I/O 작업에도 영향을 미칠 수 있습니다. 예를 들어, 디스크 I/O 작업 중 네트워크 호출이 발생하면 브리지 네트워크 모드의 추가적인 오버헤드로 인해 디스크 I/O 성능이 저하될 수 있습니다.
네트워크 모드에 따라 CPU 및 메모리 자원의 사용 패턴이 달라질 수 있습니다. 호스트 네트워크 모드는 네트워크 오버헤드가 적어 CPU와 메모리를 더 효율적으로 사용할 수 있습니다. 반면, 브리지 네트워크 모드는 네트워크 트래픽을 처리하는 데 더 많은 자원을 사용하게 되어 디스크 I/O 작업에 사용될 수 있는 자원이 줄어들 수 있습니다.
호스트 모드에서는 컨테이너가 호스트의 네트워크 인터페이스를 직접 사용하여 네트워크 경로가 단축되므로, I/O 경로의 복잡성이 줄어듭니다. 이로 인해 네트워크 트래픽과 디스크 I/O 작업이 동시에 발생할 때, 호스트 모드에서 더 효율적인 자원 관리를 통해 디스크 I/O 성능이 향상될 수 있습니다.
실제 네트워크 트래픽이 디스크 I/O 성능에 간접적으로 영향을 미칠 수 있습니다. 브리지 모드에서는 네트워크 트래픽이 더 많은 오버헤드를 유발하여 디스크 I/O 성능을 저하시킬 수 있습니다. 반면, 호스트 모드에서는 네트워크 트래픽 처리가 더 효율적이므로 디스크 I/O 성능에 덜 영향을 미칠 수 있습니다.
네트워크 모드가 디스크 I/O 성능에 미치는 영향은 여러 요인에 의해 복합적으로 발생할 수 있습니다. 네트워크 스택의 간섭, 스토리지 드라이버의 동작 차이, 자원 경쟁 등의 이유로 인해 디스크 I/O 성능이 달라질 수 있습니다. 따라서, 최적의 성능을 위해서는 애플리케이션의 특성과 사용 사례에 따라 적절한 네트워크 모드를 선택하는 것이 중요합니다.