1. nginx 디렉토리 권한 수정
RHEL9 에서 dnf로 nginx를 설치하면 nginx 디렉토리들에 대한 권한이 default로 root로 들어가 있을 것이다. 이는 보안상 취약하므로, 아래 명령어를 수행하여 ‘nginx’ 유저 및 그룹으로 변경해 주도록 하자.
# /etc/nginx/ 디렉토리 권한을 nginx:nginx로 변경
chown -R nginx:nginx /etc/nginx/
2. SELinux 설정 변경
SELinux는 최신 RHEL 및 CentOS 에서 기본적으로 활성화되어 있는 Linux 시스템용 보안 아키텍처이다. SELinux 모듈이 켜져 있으면, 파일 접근 권한, Upstream 위치로의 Proxy 및 소켓을 통한 다른 프로세스와 통신하는 것 등에 제한이 따르기 때문에, nginx 도 구동부터 막히게 된다.
따라서 아래 명령어 및 파일 수정으로 SELinux를 enforcing → permissive 로 변경해보자!
# 현재 SELinux 상태 확인 명령어
sestatus
서버를 처음 셋업하고 SELinux에 대한 설정을 건드리지 않았다면, 위와 같이 Current mode가 enforcing
일 것이다. 이 상태면, systemctl start nginx
가 되지 않는다.
# 현재 SELinux 를 enforcing -> permissive 로 변경
setenforce 0
# SELinux 상태 다시 확인
sestatus
이제 nginx 는 구동시킬 수 있을 것이다.
문제는 이 서버가 재부팅되면, 다시 SELinux의 config 파일을 읽어와서
enforcing
모드로 될 것이므로, 아예 config 파일에서도 permissive
로 설정을 바꿔보자!
# RHEL9 기준, vim /etc/selinux/config
# /etc/selinux/config 파일
# 아래와 같이 'SELINUX' 값을 'permissive'로 변경
SELINUX=permissive
SELinux 의 config 파일을 permissive로 변경 후에 다시 sestatus 명령어를 수행해보면, 위 그림과 같이 Mode from config file 의 값이 permissive로 변경된 것을 확인할 수 있다!
3. nginx 기본 설정 파일 수정 - nginx.conf
# nginx 설치 경로로 이동
cd /etc/nginx
# nginx.conf 파일 수정 전 원본 복사
cp nginx.conf nginx.conf_ori
# nginx.conf 파일을 수정
vim nginx.conf
# nginx.conf
# worker 프로세스를 실행할 사용자 설정
# 이 user에 따라 권한이 달라질 수 있다.
user nginx;
# 실행할 worker 프로세스 설정
# 서버에 장착되어 있는 코어 수만큼 할당하는 것이 보통.
worker_processes auto;
# 오류 로그를 남길 파일 경로 지정
# 여기에서는 /etc/nginx/log 디렉토리를 만들었다고 가정
error_log /etc/nginx/log/error.log warn;
# nginx 마스터 프로세스 ID를 저장할 파일 경로 지정
pid /run/nginx.pid;
# 접속 처리에 관한 설정
events {
# 워커 프로세스 한 개당 동시 접속 수 지정 (512 or 1024 추천)
worker_connections 1024;
}
# 웹, 프록시 관련 서버 설정
http {
# 액세스 로그 형식 지정
log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# 액세스 로그를 남길 파일 경로 지정
access_log /etc/nginx/log/access.log main;
# sendfile api 를 사용할 지 말 지 결정
sendfile on;
# on - 데이터를 클라이언트로 전송할 때 데이터를 버퍼링하여 전송 지연을 최소화.
tcp_nopush on;
# on - 작은 패킷을 빠르게 전송하여 지연 시간을 최소화.
tcp_nodelay on;
# 접속 시 커넥션을 몇 초동안 유지할 지에 대한 설정
keepalive_timeout 65;
# nginx가 MIME 타입을 해시 테이블에 저장하는 데 사용하는 메모리의 최대 크기 (default 4096)
type_hash_max_size 4096;
# mime.types 파일을 읽어온다.
include /etc/nginx/mime.types;
# MIME 타입 설정
default_type application/octet-stream;
# 보안을 위해 nginx 버전을 숨기도록 지정
server_tokens off;
# /etc/nginx/conf.d 디렉토리에 하위의 모든 .conf 파일을 읽어오도록 설정
include /etc/nginx/conf.d/*.conf;
# RHEL9 의 dnf 배포판에는 없어 별도로 디렉토리 생성 및 conf 구문 추가
# /etc/nginx/sites-enabled 디렉토리 하위의 모든 사이트 설정을 불러오도록 지정
include /etc/nginx/sites-enabled/*;
}
4. nginx-gunicorn 연동을 위한 config 파일 작성
필자는 RHEL9에서 dnf로 설치한 nginx에 sites-available 디렉토리가 따로 없었다. 그래서 별도로 생성해주었고, 해당 디렉토리는 실제 conf파일이 반영 되는 곳이 아닌, 미리 conf파일을 작성하여 저장하는 용도로 사용된다.
# /etc/nginx/sites-available 디렉토리 하위에 [gunicorn에 배포할 프로젝트 이름] 으로 설정 파일을 만들 것이다.
# 예시 : /etc/nginx/sites-available/test_project
server {
listen 80;
server_name [IP address | Domain name];
location / {
include proxy_params;
proxy_pass http://unix:/apps/venvs/{프로젝트의 가상환경명}/run/{프로젝트명}.sock;
}
}
location /
: 모든 요청을 구문에 맞게 리다이렉션 하라는 의미
http://unix:/~.sock
: 해당 요 청을 Gunicorn의 Unix Domain Socket으로 보내라는 의미
5. proxy_params 파일 작성
위 test_proejct에 대한 config 파일에 include proxy_params 구문이 있는데, 우리는 해당 파일을 아직 작성하지 않았다. proxy_params 파일은 해당 사이트에 대한 별도의 프록시 파라미터 설정을 해주는 파일이다. 다음과 같이 작성해보도록 하자.
# vim /etc/nginx/proxy_params
# proxy_params 파일
# Request를 넘겨 받을 때 아래 Proxy Header 정보를 지정
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-NginX-Proxy true;
# 서버(웹사이트)에 업로드 가능한 파일 최대 용량
# default : 1M
client_max_body_size 128M; #
클라이언트 Buffer 사이즈 (body 들어오는 사이즈)
# default : 32bit=8k, 64bit=16k
client_body_buffer_size 1M;
# 백엔드 서버로부터 해당 response를 버퍼링할 지 여부 정의
# on : 버퍼가 제공하는 메모리 공간을 이용하여 응답 데이터를 메모리에 저장
# off : 응답을 직접 클라이언트에게 전달
proxy_buffering on;
# 백엔드 서버로부터 response 데이터를 읽는 데 사용할 버퍼의 수와 크기 설정
proxy_buffers 256 16k;
# 백엔드 서버 response의 첫 부분을 읽기 위한 버퍼 크기 지정
proxy_buffer_size 128k;
# 여기서 지정한 값을 버퍼가 초과하면, 데이터를 클라이언트로 보내고 버퍼를 비움
proxy_busy_buffers_size 256k;
# 한 번에 사용할 수 있는 임시 파일 용량
proxy_temp_file_write_size 256k;
# 최대 사용할 수 있는 임시 파일 용량
proxy_max_temp_file_size 1024M;
# 백엔드 서버 접속 제한 시간(sec 단위)을 정의
proxy_connect_timeout 3600;
# 백엔드 서버로부터 데이터를 읽을 때의 제한 시간
proxy_send_timeout 3600;
# 백엔드 서버로 데이터를 전송할 때의 제한 시간
proxy_read_timeout 3600;
# 백엔드 서버가 보내오는 모든 에러 페이지를 nginx가 직접 클라이언트에게 회신
proxy_intercept_errors on;
6. sites-enabled에 링크 생성을 통한 프로젝트 config 파일 적용
sites-available 에서 작성한 프로젝트에 대한 config파일을 sites-enabled 디렉토리 하위에 심볼릭 링크로 만들어 해당 config가 적용되도록 한다. nginx는 이러한 방식을 통해, 현재 서버에 적용되고 있는 config 파일을 직접 수정하지 않고, 별도의 파일로 작성 후에 바로 적용할 수 있도록 구현되어 있다.
# 실제 nginx에서 가져오는 설정 파일인 sites-enabled 하위 파일을 심볼릭 링크로 생성
ln -s /etc/nginx/sites-available/test_project /etc/nginx/sites-enabled/test_project
# 생성된 심볼릭 링크에 대한 소유권을 'nginx' 로 변경
chown -h nginx:nginx /etc/nginx/sites-enabled/test_project
7, gunicorn 전용 계정 생성 및 권한 부여
python과 관련된 디렉토리인 /apps와 그 하위 디렉토리들에 대한 권한을 모두 ‘gunicorn’이라는 계정으로 소유권을 이전할 것이다. 일단 OS 계정부터 만들어야 하니, 다음 명령어를 수행하여 계정을 만들자.
# 아래 명령어는 모두 root 또는 root 계정과 동등한 권한으로 수행
# 'gunicorn' Linux 계정 생성
useradd gunicorn
# 'gunicorn' Password 설정
passwd gunicorn
# python과 관련된 디렉토리인 /apps 및 그 하위 디렉토리의 소유권을 모두 gunicorn으로 변경
chown -R gunicorn:gunicorn /apps
# 'gunicorn' 홈 디렉토리 설정
usermod -d /apps gunicorn
# 'gunicorn'이 구동되면서 생성될 소켓 파일이 위치할 디렉토리 생성
# 'gunicorn' 으로 계정 전환하여 생성하기
mkdir /apps/venvs/test_project/run
8. gunicorn의 socket 파일에 대한 보안 컨텍스트 변경
nginx ↔ gunicorn 간 통신을 TCP가 아닌 Unix Domain Socket을 이용하여 셋업하고자 한다.
필자의 경우 nginx 와 gunicorn을 같은 서버에 둔다고 가정하여, 불필요한 네트워크 오버헤드를 없애기 위해 로컬 환경 안에서의 UDS(Unix Domain Socket)을 통한 통신으로 설정하는 것이다.
우선 /run/test_project.sock 파일이 생성되려면, gunicorn을 UDS 방식으로 한번 구동시켜줘야 한다.
gunicorn이 이미 실행되어 있다면 종료시키고, 다음 명령어로 gunicorn을 다시 구동시키자.
# python 프로젝트에 대한 venv 활성화 및 프로젝트의 홈 디렉토리에서 수행
# "pybo:create_app()" 구문은 배포하는 애플리케이션에 맞춰 지정해야 한다.
gunicorn --bind unix:/apps/venvs/test_project/run/test_project.sock "pybo:create_app()" &
# /run/test_project.sock 파일 생성 확인 후, gunicorn 종료
kill -9 [gunicorn PID]
gunicorn의 UDS 소켓 파일의 보안 컨텍스트를 변경하지 않으면, nginx 의 포트(IP:80)를 통한 접속 시,
502 Bad Gateway에러와 nginx의 에러 로그에서는
Connection refuesed에러를 마주할 것이다.
정석이라면 /apps/venvs/test_project/run/test_project.sock 파일의 보안 컨텍스트를 변경해줘야 하지만,
그냥 더 쉽게 ‘nginx’ 계정이 ‘gunicorn’ 그룹에 들어가도록 하자.usermod -G nginx gunicorn
→ 이렇게 하면, nginx가 'gunicorn' 계정과 동일한 그룹으로 구동되어, gunicorn에 의해 생성되는 sock 파일을 접근할 수 있게 된다.
2. 작성한 nginx의 모든 config 파일 syntax 검사
nginx -t
위와 같이 syntax is ok
및 test is successful
이라는 문구가 나타났다면, nginx config 파일의 syntax error는 없다고 볼 수 있다.
10. nginx 구동
config 파일에 이상이 없으니, 이제 nginx 를 구동하여 WSGI서버와 연동이 잘 되었는지 확인해보자!
systemctl start nginx
11. {IP}:80 를 통한 접속으로 test_project Web App 접속 여부 확인
nginx 및 gunicorn이 10.123.123.123 서버에서 구동된다면, http://10.123.123.123:80/ 로 접속하여 gunicorn으로 구동하는 애플리케이션의 인덱스 페이지가 나타나는 지 확인하면 된다.
12. gunicorn service 등록
systemctl 명령어로 gunicorn을 쉽게 올리고 내릴 수 있도록, service 파일로 작성해보자.
서비스 파일에 사용될 환경변수가 선언된 파일이다. 이는 해당 프로젝트의 venv 디렉토리에 위치시키도록 하자.
# /apps/venvs/test_project/test_project.env 파일
FLASK_APP=pybo
FLASK_DEBUG=false # 운영 환경이므로 디버깅 모드는 false로 한다.
APP_CONFIG_FILE=/apps/projects/test_project/config/production.py
이제 서비스 파일을 작성해보자. python의 경우 venv를 프로젝트 단위로 구성하였으므로, 서비스 파일 또한 gunicorn으로 구동시킬 프로젝트 단위로 생성해야 함을 주의하자!
# gunicorn으로 구동 시킬 프로젝트에 대한 서비스 생성 (root 계정 또는 그와 동등한 계정으로 수행)
# 위 예시와 맞추기 위해 service 명을 'test_project'로 지정
vim /usr/lib/systemd/system/test_project.service
# test_project.service 파일
[Unit]
Description=gunicorn daemon
After=network.target
[Service]
User=gunicorn
Group=gunicorn
WorkingDirectory=/apps/projects/test_project
EnvironmentFile=/apps/venvs/test_project/test_project.env
ExecStart=/apps/venvs/test_project.sh
ExecStart=
ExecStart=/apps/venvs/test_project/bin/gunicorn \
--workers 2 \
--bind unix:/apps/venvs/test_project/run/test_project.sock \
--access-logfile /apps/venvs/test_project/logs/access.log \
--error-logfile /apps/venvs/test_project/logs/error.log \
"pybo:create_app()"
[Install]
WantedBy=multi-user.target
참고
https://velog.io/@odh0112/Django-Nginx-Gunicorn-%EC%97%B0%EB%8F%99-2-fb00a9kg
https://kscory.com/dev/nginx/setting
Uploaded by
'Middleware > WSGI' 카테고리의 다른 글
[Gunicorn] logrotate, crond를 활용한 nginx와 gunicorn의 log 파일 관리하기 (1) | 2023.11.02 |
---|---|
[Gunicorn] RHEL9, Python 3.11 환경에서 Gunicorn(구니콘) 설치 (1) | 2023.10.29 |
최근댓글