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

    반응형

     

     

     

    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차 수정을 한 것이고 분명 이 것보다 더 좋은 방법이 있을 거라고 생각합니다

     

     

     

    반응형

    댓글