Project/WEB IDE

Docker 내부에 코드 복사 및 실행 | 터미널 입력 줄바꿈 처리 문제 | Web IDE 구축하기 - 4

경아 (KyungA) 2024. 5. 12. 18:34
반응형

 

 

 

Docker 컨테이너에서 터미널을 입력할 때 줄바꿈 처리의 문제


현재 코딩테스트 input이 될 값을 띄어쓰기 정도만 포함된 문제로밖에 못하는 것입니다. "3" 또는 "3 7 4" 이런 값은 가능한데, "1 2 3\n5 6 7" 이런 값이 안됩니다. 쉘 명령어를 echo -e "1 2 3\n5 6 7" 이렇게 작성을 해보았으나, 클라이언트 측에서 출력할 때 -e 1 이렇게만 출력됩니다. 컨테이너 안에서 shell 명령어가 실행되거나, shell 파일을 읽어오도록 수정해 보았으나 해결되지 못했고, bash 에러가 떠서 이 부분도 수정을 해보았으나, 아직까지 해결하지 못했습니다. 이 부분은 공부가 좀 더 필요할 것 같습니다

위에는 제가 이전에 프로젝트를 하다가 해결하지 못한 부분이었습니다

해당 프로젝트의 실행 순서는 이렇습니다

  • 클라이언트는 매개변수를 출력할 수 있다
  • 클라이언트는 매개변수를 이용해서 문제에 맞게 코드를 작성한다
  • 클라이언트는 결괏값을 출력한다
  • 서버는 클라이언트에게 받은 code를 docker 이미지로 빌드한다
  • docker 이미지를 실행시켜 컨테이너를 띄운다
  • 이 과정에서 테스트케이스는 여러개이며 매개변수가 동적으로 할당되고 테스트케이스마다 컨테이너가 실행되어야 한다

기존에 해당 로직을 타는 코드는 아래와 같이 작성되었습니다

// Dcokerfile
FROM python:3.9-alpine
COPY compile/code.py  /usr/src/
WORKDIR /usr/src/
CMD ["python3", "code.py"]


// 컨테이너 실행 명령어
const command = `echo ${test.input} | docker run --rm -i python:3`;

// test.input에 들어갈 동적 매개변수 형태
test.input = "1 2 3\n4 5 6"

이렇게 작성해보았을 때 한 줄로 입력된 매개변수 값을 echo 명령어로 실행시킬 땐 문제가 없었으나

줄바꿈 개행이 들어갔을 때 문제가 생겼습니다

여러 가지를 시도를 해봤으나 뜨는 이슈는 아래와 같습니다

  • "1 2 3 까지만 출력됨
  • 1 2 3 까지만 출력됨
  • 1 2 3\n 까지만 출력됨
  • -e 1 출력됨
  • python 실행 시 EOFError: EOF when reading a line 에러
  • Command failed: docker run -e GREETING="1 2 3 4 5 6" -i python:3 "docker run" requires at least 1 argument. See 'docker run --help'. Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] Create and run a new container from an image 에러 반환

해결방법


그 이후로 docker 강의를 듣고 -i, -t 옵션을 알게 되면서 코드를 수정해 보게 되었습니다

일단 결론부터 말하자면 제 쪽 자바스크립트 코드 문제, 클라이언트 쪽에서 작성한 코드의 문제였습니다

수정한 코드는 아래와 같습니다

// Dcokerfile
FROM python:3.9-alpine
COPY src/docker/input.sh /app
COPY compile/code.py  /app
WORKDIR /app
CMD ["sh", "-c", "./input.sh | python3 code.py"]

// input.sh
echo -e "$ARGS"

// 컨테이너 실행 명령어
const command = `docker run -e ARGS="${test.input}" -i python:3`;

// test.input에 들어갈 동적 매개변수 형태
test.input = "1 2 3\\n4 5 6"

사실상 기존 코드에서 많이 바꿀 필요도 없었고... 제가 삽질한 거였습니다

일단 이처럼 코드가 바뀐 이유는 여러 가지  테스트해보다가 나오게 된 거고 굳이 저렇게 작성 안 해줘도 되더군요...

그냥 제 기존 코드에서 몇 가지만 살짝 수정하면 되었던 문제였습니다

여기서 눈여겨봐야 할 포인트는 아래와 같습니다

  • echo -e 옵션 ( -e 옵션은 echo 명령어가 \n이 개행 문자로 해석되어 출력됩니다)
  • test.input을 따옴표로 감싸줘야 하는 것 (큰따옴표, 작은따옴표 크게 상관없는 것 같습니다)
  • 매개변수 string의 개행문자 \n에 \를 한번 더 써줘야 할 것

그리고 정답을 출력해 내는 클라이언트 쪽 코드도 수정해줘야 합니다

제가 python을 잘 몰라서 str = input() print(str) 이런 식으로 받아서.... 삽질한 케이스인데요

이러니까 계속 첫 번째 줄만 출력되는 거였습니다..... (그때 다른 식의 코드도 써본 것 같은데 잘 안 됐던... 뭐지...)

import sys
for line in sys.stdin:
    print(line, end="")

위와 같이 여러 줄의 문자열을 출력해 주는 코드로 작성해 주니까 해결되었습니다

 

 

.

Docker 내부에 코드 복사 및 실행


Docker 실행될 때 굉장히 느립니다. 이 부분은 제 개인 노트북 문제일 확률이 높다고는 생각이 듭니다.

유튜브에 해당 프로젝트 공부 브이로그를 올렸더니

댓글로 도커 이미지를 매번 빌드할 필요는 없을 것 같다는 댓글이 많이 달렸습니다

저 또한 로컬에서 너무 느려서 개발하면서 불편한 상태였고요

아직 도커에 대해 잘 모르는 저로선, 유저가 보낸 코드가 달라질 때마다 매번 빌드하는 건 당연한 건 아닌가?라고 생각을 했습니다만

도커 강의를 듣고 나니 제가 많이 몰랐구나라는 생각이 들었습니다

// 각 언어마다 도커 파일
FROM node:16-alpine
COPY src/docker/input.sh /usr/src/
COPY compile/code.js /usr/src/
WORKDIR /usr/src/
CMD ["sh", "-c", "./input.sh | node code.js"]

// 이미지 빌드
execSync(`docker build -t ${build[lang].name} -f ${build[lang].path} .`);

// 컨테이너 실행
exec(`docker run --rm -e ARGS="${test.input}" -i node:16`);

기존에는 유저가 코드를 제출할 때마다 위 코드가 실행이 되었습니다

그래서 매번 <none>이라는 이름의 이미지가 생성되기도 했고,

각 언어마다 도커 이미지가 별도로 생성되는 형태,

컨테이너 또한 실행할 테스트케이스마다 생성되고 실행되었기에 거기에서 오래 걸리는 형태였습니다

그래서 이걸 아래와 같이 리팩토링 했습니다

 

개선사항


FROM python:3.9-alpine
RUN apk add --update nodejs npm
RUN apk add openjdk11
RUN apk add g++
COPY src/docker/input.sh /usr/src
WORKDIR /usr/src
VOLUME ["/app"]

일단 가장 먼저 모든 언어를 실행시킬 수 있는 환경을 만들었습니다

해당 도커 이미지는 Docker hub에 올려놓은 상태입니다

router.get("/:problemId", (req: Request, res: Response) => {
  try {
    const imageExists = execSync(`docker images -q kyunga/all-lang:latest`)
      .toString()
      .trim();

    if (!imageExists) {
      dockerBuild();
      execSync(`docker run --rm -d -it --name test-app kyunga/all-lang:latest`);
    } else {
      exec(`docker run --rm -d -it --name test-app kyunga/all-lang:latest`);
    }

   // ..
});

그리고 해당 문제 페이지에 접근하자마자 이미지 빌드 및 컨테이너 실행을 합니다

여기서 문제는 해당 이미지가 이미 있으면 에러가 발생하기 때문에

이미지가 있으면 컨테이너만 실행시키고, 이미지가 없으면 둘 다 진행합니다

 // 유저의 코드를 파일화
  fs.writeFileSync(`${filePath}/${fileName[lang]}`, code);
 // 파일화 끝나면 컨테이너 내부에 복사
 execSync(`docker cp compile/. test-app:/usr/src`);

유저가 코드를 제출하면 먼저 파일화를 동기로 진행합니다
(동기로 진행하지 않으면 에러 반환)

그다음 컨테이너 내부에 복사합니다 여기서 중요한 게 해당 컨테이너는 실행 중이어야 합니다
(-it 옵션이 들어간 이유!)

execSync(`docker exec test-app sh -c "g++ -o main main.cpp"`);
exec(`docker exec test-app sh -c "echo -e '${test.input}' | ./main"`);

그리고 각 언어마다 위와 같이 실행해 줍니다

execSync는 모든 언어마다 필수는 아니고 java, cpp처럼 코드를 컴파일해야 하는 경우에만 필수입니다

이렇게 작성해 주니 각 언어마다 Dockerfile이 있을 필요도 없어졌고,

훠어어어어얼씬 속도가 빨라졌습니다!!!!!!!!!!!!!!!!!!!!!

사실 도커 강의에서 cp 명령어는 일반적인 경우는 아니다, 웬만해선 안 쓰는 거 추천한다라는 강사의 말이 있었으나,

저는 현재까지 배운 내용으로 토대로 1차 수정을 한 것이고 분명 이 것보다 더 좋은 방법이 있을 거라고 생각합니다

 

 

 

반응형