
큰 기능이 들어가지 않고, 한번 개발이 끝나면 수정이 크게 들어가지 않는 기능을 수행하는 서버를 FastAPI를 이용하여 만들기로 했다. (사실 파이썬으로 백엔드 서버를 처음 개발하게 됐기에 쉬운거부터 접근한게 크다.)
하지만 배포 과정에서 이런저런 이슈 사항들이 있었다.
우선 현재 프로젝트에서는 Docker를 적용하지 않았다. 그래서 일단은 Docker 이미지로 배포하는 방법은 배제하고 생각하기 시작했다.
그러면 다음으로는 어떻게 해야되지.
우선 기능이 똑바로 돌아가는 것을 로컬로는 확인했으니, 프로젝트 디렉토리를 통째로 서버에 업로드했다.
TMI: 나는 SSH툴로 Windows환경에서는 MobaXTerm을, Mac에서는 Termius를 주로 사용한다.
그다음 백그라운드에서 돌려봤다.
nohup uvicorn app.main:app --ssl-keyfile=./key.pem --ssl-certfile=./cert.pem --host 0.0.0.0 --port 8000 &
잘 돌아가고, 호출 또한 잘된다. 외부에서 바로 호출을 해봐도, 내부의 다른 서버에서 호출을 해봐도 전부 다 잘 돌아가서 한시름을 놨는데, 뭔가 찝찝하다.
Java의 .jar파일처럼 실행 파일을 따로 만들어서 배포하는 것도 아니고, 디렉토리 통째로 업로드라니…
뭔가 탐탁치 않다.
이 방법은 재배포할 일이 많이 없다지만 보안, 관리상 용이하지 않을 것 같았다. 하지만 큰 제약사항이 두 가지 있는데…
Docker를 사용하지 않아, 이거 하나때문에 Docker를 적용하기에는 너무 오버다.FastAPI는 Springboot처럼 따로 실행파일을 생성할 수 없다.그래서 우선은 Docker사용을 아예 배제하고 시작해본다. (선임께 얘기했더니 과하다고 컷하신건 비밀)
PyInstaller Manual — PyInstaller 6.13.0 documentation
root directory에 app.spec 파일을 추가하고,
# -*- mode: python ; coding: utf-8 -*-
a = Analysis(
['app/main.py'],
pathex=[],
binaries=[],
datas=[
('<Personal Route>\\AppData\\Local\\Programs\\Python\\Python312\\Lib\\site-packages\\jamo\\data\\*.json', 'jamo/data'),
('app/libs/g2pk/rules.txt', 'g2pk'),
('app/libs/g2pk/idioms.txt', 'g2pk'),
('app/libs/g2pk/table.csv', 'g2pk'),
('cert.pem', '.'),
('key.pem', '.'),
('.env', '.')
],
hiddenimports=[
'jamo',
'jamo.jamo',
'jamo.compat',
'jamo.data'
],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='app',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=True,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
)
파일 생성 후, 파일 내용 설정
pyinstaller app.spec
/dist/app.exe 와 같이 dist directory에 app.exe가 생성되는데, 개발 서버는 Linux이다. 그래서 PyInstaller로 .sh파일을 생성하여 리눅스용 실행 파일을 만들어보려 했는데, Windows에선 리눅스 실행파일이 생성되지 않는다.
그래서,
WSL을 설치했다.
윈도우에서 리눅스를 실행하게 해주는 프로그램인데, 이후 과정을 요약해서 정리하자면
./app.sh실행이 잘됐다. 그러면 이제 이걸 개발서버에 올려보았지만
“glibc 버전이 맞지 않아 실행이 안된다.”
[PYI-1436136:ERROR] Failed to load Python shared library '/tmp/_MEIFJuMgk/libpython3.12.so.1.0': dlopen: /lib/x86_64-linux-gnu/libm.so.6: version `GLIBC_2.38' not found (required by /tmp/_MEIFJuMgk/libpython3.12.so.1.0)
개발서버에 설치된 Ubuntu 버전이 낮아 이슈로 할 수가 없다.
오케이, 그렇다면 docker로 ubuntu 버전울 낮춰서 빌드해보자.
에러 메세지는 깜빡하여 복사하지 못했지만, docker에서 이미지 만드는 중에 모듈 데이터 일부가 경로 다름 이슈가 발생했다. 이 이슈를 해결한 DockerFile을 작성해보자.
# 실행파일 빌드
FROM ubuntu:20.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y python3 python3-pip python3-venv build-essential
WORKDIR /app
COPY . /app
RUN python3 -m venv venv
RUN . venv/bin/activate && \
pip install --upgrade pip && \
pip install -r requirements.txt
RUN pip install jamo
RUN pip show jamo
RUN pip install pyinstaller && \
pyinstaller app.spec
docker build -t formatter-builder .
docker create --name extract formatter-builder
docker cp extract:/app/dist/app ./dist
docker rm extract
cd dist/app
chmod +x app
./app
일단 결국 빌드되고 실행은 되었다. 하지만 install해뒀던 모든 라이브러리를 잊고야 만 실행파일…
~$ ./app
Traceback (most recent call last):
File "app/main.py", line 1, in <module>
ModuleNotFoundError: No module named 'fastapi'
[PYI-16153:ERROR] Failed to execute script 'main' due to unhandled exception!
이 정도 오면 해결해도 다른 협업하는 분들이 배포 과정을 따라오고 이해하기 힘들다. 수정해야 될 상황이 발생하면 무조건 내가 해야 되는 상황이 발생하기 때문에,
결국 프로젝트 디렉토리 전부 올려서 해결하는 것으로 결론지었다. (굉장히 맘에 안 들지만)
배포 과정을 그나마 단순화하기 위해 shell 실행파일을 작성했다.
#!/bin/bash
echo "> pid Check"
CURRENT_PID=$(lsof -i | grep uvicorn | grep -v grep | awk '{print $2}')
echo "> current pid: $CURRENT_PID"
if [ -z "$CURRENT_PID" ]; then
echo "> No Process"
else
echo "> sudo kill $CURRENT_PID"
sudo kill "$CURRENT_PID"
sleep 4
if ps -p "$CURRENT_PID" > /dev/null; then
echo "> server process $CURRENT_PID is not dead"
echo "> sudo kill -9 $CURRENT_PID"
sudo kill -9 $CURRENT_PID
sleep 2
fi
fi
echo "> installing requirements..."
pip install -r ./<directory name>/requirements.txt
echo "> server deploy"
cd <directory name>/
nohup /home/<username>/.local/bin/uvicorn app.main:app \
--ssl-keyfile=./key.pem \
--ssl-certfile=./cert.pem \
--host 0.0.0.0 \
--port 8000 > ../nohup.out 2>&1 &
추후에는 미리 이슈사항들을 예측하고 버전과 배포 과정부터 제대로 체크를 한 후 작업에 들어갈 것 같다. 너무 단편적으로만 고려해서 너무 아쉬운 것 같다.
질문과 피드백은 언제든 환영입니다. :)
안녕하세요! 링크드인 포스트를 보고 댓글을 남깁니다. 먼저 이전에 올려주신 FastAPI 관련 포스트들이 큰 도움이 되었습니다. 감사합니다.
포스트 초반에 'Docker를 적용하지 않았다'는 언급이 있는데, 그렇다면 서버가 컨테이너에서 구동되는 것이 아니라 서버 컴퓨터 저장소에서 가상환경 형태로 구동되는 것인가요?
그리고 Docker를 맨 처음에 고려하지 않으신 이유도 궁금합니다!