Nginx의 주요 설정정보는 /etc/nginx/nginx.conf
에 존재합니다. 하지만, 때에 따라서 include /etc/nginx/conf.d/*.conf;
와 같이 include
지시자를 사용해서 특정 파일을 nginx.conf에 포함하여 적용할 수 있습니다.
그래서 이번 포스트는 nginx.conf에
include /etc/nginx/conf.d/*.conf
를 사용하였다가, 문제가 생겼던 상황에 대해 분석해보려고 합니다. (동아리 신입부원의 문제를 함께 해결해주다가 알게되었습니다)
저희 동아리에는 nginx를 이용하여 특정 express 서버로 proxy + loadbalancing 하는 온보딩 과제가 있습니다. 이 때, docker-compose 를 이용하여, nginx와 express서버 2개를 띄우고, 요청을 nginx 설정 정보로 두개의 서버에 분산하는 과제입니다.
여기서 문제는 다음과 같습니다.
docker-compose에서 nginx의 port를 80:8000으로 열고, nginx.conf에서 listen을 8000으로 받으면 정상적으로 /api 요청이 proxy됨
하지만, docker-compose에서 nginx의 port를 80:80으로 열고, nginx.conf에서 listen을 80으로 받으면 /api 요청이 proxy되지 않음. 2024/04/06 01:35:01 [error] 30#30: *3 open() "/usr/share/nginx/html/api/hi" failed (2: No such file or directory), client: 172.17.0.1, server: localhost, request: "GET /api/hi HTTP/1.1", host: "localhost"
아무리 생각해도 2번의 문제를 알 수 없었습니다. 아래는 nginx.conf의 설정 정보입니다. 80:80으로 컨테이너 포트를 매핑시키고, listen을 80으로 설정했을 때, 요청이 proxy되지 않습니다.
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream backend { # expressjs is a arbitrary upstream name.
ip_hash; #ensures sticky session
server expressjs1:3000; # docker-compose.yml에서 올라가는 컨테이너명으로 작성.
server expressjs2:3000;
keepalive 1024;
}
server {
listen 80; # nginx를 통해 외부로 노출되는 port.
location / {
root /usr/share/nginx/html/;
}
location /api/ {
proxy_pass http://backend/api/; # uses the already-set upstream
}
}
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 /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
include /etc/nginx/conf.d/*.conf;
}
문제가 되었던 부분은 include /etc/nginx/conf.d/*.conf
입니다. 또한, 추가적으로 알아야 하는 부분은 nginx의 블록별 상속 관계입니다.
include
지시자는, 설정정보를 유연하게 가져가기 위해서 나온 지시자 입니다. nginx를 위한 여러가지 설정정보가 늘어남에 따라, nginx.conf 파일 내부에서 외부 파일을 끌어다가 가져올 수 있습니다. 아래는 공식문서 내용입니다.
To make the configuration easier to maintain, we recommend that you split it into a set of feature‑specific files stored in the /etc/nginx/conf.d directory and use the include directive in the main nginx.conf file to reference the contents of the feature‑specific files.
하지만, 위에서 설정했던 include에 포함되었던 파일은 다음과 같습니다.
server {
listen 80;
listen [::]:80;
server_name localhost;
#access_log /var/log/nginx/host.access.log main;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
주석을 모두 제거해서 매우 간촐해보입니다. 직접 확인하고 싶으시면 직접 nginx 컨테이너를 띄워서 확인해보시면 됩니다.
이러한 파일이 nginx.conf에 포함이 되었다는 점입니다. 기본적으로 http port번호를 listen하고 있습니다. 하지만 이 포트는 nginx.conf에서도 80으로 listen하고 있습니다.
여기서 제가 착각했던 부분이, nginx.conf의 server 블록에서 상위 block인 http의 include 파일 정보를 가져다 쓸거라고 생각하지 않았다는 점입니다. 즉, http 블록과 다른 block처럼 행동할 것이라고 생각했고, default.conf 정보를 공유하지 않을 것이라고 생각했습니다.
하지만 '공유'합니다. 정확히 말하자면, 상위 블록의 내용이, 하위 블록의 내용에서 override 되지 않는 이상, 그대로 하위 블록까지 상속됩니다.
결국 문제 상황은, nginx.conf가 default.conf정보를 들고 오면서, server {} 블록의 정보가 그대로 override 되었다는 점입니다. 따라서 nginx.conf의 proxy가 되지 못하고, default.conf 정보로 다 덮어 씌워진 것이었습니다.
include를 제거해서 default.conf정보를 아예 사용하지 않게 하는 방법
port가 80번으로 겹쳐서 일어나게 된 일이니, port를 80번 말고 다른 port로 host의 80번 요청과 매핑한다면, 문제가 없을 것입니다.
기본적으로 include
는 유연한 nginx의 설정을 위해서 등장하였습니다. 굳이 이를 사용하지 않을 이유는 없습니다. 그렇다면, 커스텀한 default.conf 파일에 기존에 정의한 nginx.conf의 server listen 정보를 정의하고, nginx.conf는 커스텀 설정 파일 정보를 include한 기본 nginx.conf파일만 사용하면 될 것입니다.
코드로 보면 다음과 같습니다.
default.conf (파일 이름은 뭐가 됐든 상관없이 nginx에 include 되기만 하면 됩니다.)
server {
listen 80;
location / {
root /usr/share/nginx/html/;
}
location /api/ {
proxy_pass http://localhost:3000; # uses the already-set upstream
}
}
nginx.conf (컨테이너 이미지의 기본 nginx.conf 파일입니다)
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
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 /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
include /etc/nginx/conf.d/*.conf;
}
include 경로에 커스텀한 default.conf 파일을 지정하면 default.conf를 사용하면서도, 80번 포트를 listen하여 알맞게 프록시할 수 있습니다.
이 때, 주의할 점은, include /etc/nginx/conf.d/*.conf;
여기에 nginx의 기본 default.conf 파일도 포함됩니다. 이 파일을 커스텀한 파일 정보로 덮어 씌워서 include하거나, 기본 default.conf파일을 제외 하고 커스텀한 설정 파일 정보만 include해주셔야 합니다.
저는 간단히 테스트 해보기 위해 따로 docker-compose를 사용하지 않고, Dockerfile로만 nginx 컨테이너를 띄웠습니다. 아래처럼, 작성을 하면 기존 default.conf파일이 저의 커스텀 파일 정보로 덮어 씌워져서 원하는 대로 동작할 수 있습니다.
FROM nginx
COPY $PWD/default1.conf /etc/nginx/conf.d/default.conf
하지만 COPY나 MOUNT를 default1.conf 와 같이 해버리면, include 지시자가 *.conf
형식의 파일들을 다 포함하므로, 설정 정보가 중복되어 충돌날 수 있습니다. 따라서 이 점에 유의하셔야 합니다.