flowchart TD
A[Client] -->|HTTP req| B[Nest Server]
B --> |HTTP res|A
B --> |SSH|C[Container Server] --> |SSH|B
전체 서버 구조
기존에는 SSH를 통해서 컨테이너 서버로 접속한 뒤 명령을 수행했는데, docker exec와 ssh 둘 중 하나가 병목일 것으로 예상하고 있었습니다. 범위를 좁히기 위해 함수 시간 측정하는 데코레이터를 붙여 보았고, 대부분의 병목은 SSH임을 알았습니다.
기존에는 당연히 SSH가 빠를 것이라고 생각했고 다른 선택지가 없는 줄 알았는데, 오히려 속도 측면에서는 약 200ms 정도의 지연을 항상 가져온다는 것을 알았습니다. 따라서 기존에 300ms의 속도까지 줄였을 때의 300ms의 200ms를 SSH가 잡아먹는다는 사실을 알았습니다.
컨테이너 인스턴스에 서버 등을 추가로 구현하는 것은 그전에는 제 2안 정도로 생각했었는데, SSH가 병목임을 안 이상 컨테이너 서버에 아주 간단한 HTTP 서버를 구현하기로 했습니다. 그러면서도 너무 개발을 많이 요구하지 않게 하기 위해서 아주 간단한 서버를 구현했습니다. 이전 execSSHCommand라는 ssh 함수와 인터페이스를 맞추어 명령을 수행하는 서버입니다.
const express = require('express');
const { exec } = require('child_process');
const app = express();
app.use(express.json());
app.post('/', (req, res) => {
const command = req.body.command;
exec(command, (error, stdout, stderr) => {
res.json({ stdoutData: stdout, stderrData: stderr });
});
});
app.post('/cron', (req, res) => {
const command = req.body.command;
exec(command);
res.json({});
});
const port = 8080;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
당연히 매우 위험한 서버이기 때문에 개발을 위한 저희의 IP와 저희 NCP 서버에서만 접속할 수 있도록 네트워크 설정을 마쳤습니다.
이후 postman으로 테스트했을 때 일반 명령을 수행하는 데 100후반에서 200초반 까지 속도를 단축했습니다.
컨테이너를 생성하는 데에 400ms 가량이 소요됐기 때문에, 그리고 애초에 계획에 맞추어 컨테이너 풀을 구현했습니다. 서버 생성시에 문제 별로 컨테이너를 N개씩 생성하고 컨테이너를 할당해줄 때마다 다시 생성해서 유지합니다. 최초 docker run과 docker cp 를 비롯한 작업들을 빠르게 할 수 있습니다.
미리 생성해둔 컨테이너 덕분에 더 이상 컨테이너를 최초 생성하는 로직에 의한 시간 지연은 없어졌습니다.
첫 runGitCommand 명령만 느리다는 점을 추가로 발견했습니다.
처음에는 당연히 미리 생성한 컨테이너 할당이 문제일 줄 알았으나 그렇지 않았습니다. runGitCommand의 동작이 느린 것으로 파악됐는데, 첫 명령이라고 해서 다른 동작을 하지는 않습니다. 심지어 초기화 등 작업을 생각하면 실제 최초 명령인 것도 아닙니다.