728x90
반응형
SMALL

참고자료

 

풀스택을 위한 도커와 최신 서버 기술(리눅스, nginx, AWS, HTTPS, flask 배포) [풀스택 Part3] 강의 | 잔재

잔재미코딩 DaveLee | 본 강의는 풀스택 강의 시리즈 Part3 강의로 최신 서버 기술과 도커 기술을 탄탄하게 익히는 강의입니다. 본 강의는 실질적으로 도커를 내 기술 스택에 포함시킬 수 있도록, 도

www.inflearn.com

우선 프록시라는 단어는 매개체, 대리자이다. 그래서 nginxreverse proxy라는 단어를 종종 사용하는데 같은 의미의 프록시다.

 

Proxy Server

클라이언트가 자신을 통해, 다른 네트워크 서비스에 접속하게 해줄 수 있는 서버

Forward Proxy

클라이언트가 외부 인터넷에 직접 접근하는 게 아니라, 클라이언트가 Proxy Server에 외부 인터넷 접근 요청을 하고, Proxy Server가 외부 인터넷에 대신 접속하여 결과를 받은 후 클라이언트에게 전달하는 서버

Reverse Proxy

클라이언트가 Reverse Proxy에 요청하면, Reverse Proxy가 관련 요청에 따라 적절한 내부 서버에 접속하여 결과를 받은 후 클라이언트에게 전달

 

그러니까, 말이 조금 애매한데 하나의 네트워크 안에 존재하는 클라이언트가 해당 네트워크가 아니라 외부에 있는 서비스에 접근 요청을 할때 그 요청을 대신 해주는 것이 Forward Proxy라고 생각하면 되고, 외부에 있는 특정 호스트가 특정 네트워크에 요청을 하면 그 네트워크에 요청을 처리할 수 있는 서비스가 요청을 직접 받는게 아니라 프록시를 통해서 요청을 하고 프록시가 그 요청을 처리할 수 있는 적절한 서비스를 찾아 요청을 대신 전달해주고 받은 응답을 외부 특정 호스트에게 돌려주는 게 Reverse Proxy이다.

 

우리가 만약 서비스를 운영하고 Nginx를 사용해서 서비스를 외부에 노출 시킨다면 그건 Reverse Proxy를 사용하는 것이다. 아래와 같은 구조를 의미한다.

 

Reverse Proxy를 사용하면 어떤 장점이 있을까? 외부에서 접근하려는 서비스가 데이터베이스같은 굉장히 보안적으로 중요한 데이터라면 이 프록시가 접근을 막을 수 있다. 또한, 한 서비스에 대한 요청 트래픽이 매우 많아질 때 해당 서비스를 여러개로 복제해 로드 밸런싱도 할 수 있다. 

 

 

그러면, 이 구조 자체는 이해를 했다. 어떻게 그럼 프록시가 들어오는 요청이 A 서비스에 가야하는구나, B 서비스에 가야하는구나를 알까?

  • URI
  • 포트

이 두가지를 통해서 알 수 있다. 이제 이것도 테스트를 해보자.

 

docker-composeNginx proxy 띄우기 (포트로 서비스 구분)

우선, docker-compose 파일을 하나 준비했다.

docker-compose.yml

version: "3"

services:
    nginxproxy:
        image: nginx:1.18.0
        ports:
            - "8080:8080"
            - "8081:8081"
        restart: always
        volumes:
            - "./nginx/nginx.conf:/etc/nginx/nginx.conf"

    nginx:
        depends_on:
            - nginxproxy
        image: nginx:1.18.0
        restart: always

    apache:
        depends_on:
            - nginxproxy
        image: httpd:2.4.46
        restart: always
  • 3개의 서비스가 있다.
  • 프록시 역할을 하는 nginx, 그냥 nginx, 그냥 apache
  • 프록시 역할을 하는 nginx8080, 8081 포트를 열어두었다.
  • 프록시 역할을 하는 nginx는 볼륨 마운트도 하는데 해당 파일은 nginx 설정 파일이다.
  • 그래서 결론부터 말하면, 프록시 역할을 하는 nginx가 외부 요청에 따라 그냥 nginx 서비스를 호출하거나, 그냥 apache 서비스를 호출한다.

그래서 그 nginx 설정 파일을 열어보면 다음과 같다.

./nginx/nginx.conf

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;
    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;

    upstream docker-nginx {
        server nginx:80;
    }

    upstream docker-apache {
        server apache:80;
    }

    server {
        listen 8080;

        location / {
            proxy_pass         http://docker-nginx;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }

    server {
        listen 8081;

        location / {
            proxy_pass         http://docker-apache;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }
}

한 부분씩 자세히 알아보자.

 

events { 
    worker_connections 1024; 
}
  • events {...} 블록은 nginx의 이벤트 처리 방식을 설정하는 곳이다.
  • worker_connections 1024; 는 각 워커 프로세스가 동시에 처리할 수 있는 최대 클라이언트 연결 수를 1024로 지정한다는 의미이다. 그래서 만약, nginx가 4개의 워커 프로세스를 사용하고 있다면 최대 4 * 1024 = 4096개의 동시 연결을 처리할 수 있게 된다. 
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;
    keepalive_timeout 65;
    
    ...
    
}
  • include /etc/nginx/mime.types; → 이 설정은 nginx가 MIME 타입을 정의한 파일을 포함시키는 지시어다. 이 mime.types 파일은 다양한 파일 확장자와 그에 해당하는 MIME 타입을 매핑한 정보가 들어있다. 예를 들어, .html 파일은 text/html 타입으로, .jpg 파일은 image/jpeg 타입으로 처리되게끔 정의한 파일이다. 그래서 nginx는 이 파일을 사용해서 요청된 파일의 MIME 타입을 올바르게 설정하고, 클라이언트에 적절한 응답 헤더를 보내게 된다. 
  • default_type application/octet-stream; → 이 설정은 요청된 파일의 MIME 타입을 자동으로 결정할 수 없을 때, 기본 MIME 타입을 설정하는 지시어다. 그래서 기본 MIME 타입으로 application/octet-stream으로 지정한다는 의미이다. application/octet-stream는 기본 바이너리 데이터 타입을 의미하며, Nginx가 MIME 타입을 특정하지 못하는 파일(확장자나 내용이 불분명한 파일)을 바이너리 파일로 취급한다. 이는 파일 다운로드를 처리할 때 많이 사용되는 타입이다.
  • log_format main '...' → 로그 형식을 지정한다. main 이라는 이름으로 새로운 로그 형식을 정의하고 있다. 
    • $remote_addr: 클라이언트의 IP주소
    • $remote_user: 클라이언트가 인증된 사용자명(있을 경우)
    • $time_local: 로컬 서버 시간
    • $request: 클라이언트가 보낸 HTTP 요청 라인(메서드, URI, 프로토콜)
    • $status: HTTP 응답 상태 코드
    • $body_bytes_sent: 클라이언트로 전송된 응답 본문의 바이트 수
    • $http_referer: 클라이언트가 요청을 보낼 때 보낸 Referer 헤더 값(클라이언트가 이전에 방문한 페이지 정보)
    • $http_user_agent: 클라이언트의 웹 브라우저 및 운영체제 정보
    • $http_x_forwarded_for: 프록시를 거친 요청에 대해 클라이언트의 원래 IP 주소를 기록하는 값(프록시 서버에서 사용)
  • access_log /var/log/nginx/access.log main;  접속 로그를 기록할 파일의 경로와 사용할 로그 형식을 설정하는 지시어이다. 그래서 접속 로그가 저장될 파일 경로는 /var/log/nginx/access.log이고, 그 때 로그의 형식은 앞서 정의한 main이다.
  • sendfile on;Nginx가 파일을 클라이언트에게 전송할 때 사용하는 파일 전송 방식을 설정하는 지시어이다. on으로 설정하면 nginxsendfile 시스템 호출을 사용하여 파일 전송을 효율적으로 처리한다. 이 방식은 nginx가 파일을 직접 읽고 전송하는 대신 커널이 파일을 바로 전송하게 함으로써 성능을 크게 향상 시킬 수 있다. 주로 정적 파일(이미지, CSS, JavaScript 등)을 빠르게 전송하는 데 사용된다.
  • keepalive_timeout 65; → 클라이언트와 Nginx 서버 간 유지되는 연결의 유효 시간(초 단위)을 설정하는 지시어이다. 65초로 설정되어 있기 떄문에 클라이언트와 서버 간 연결이 65초 동안 유지됩니다. 이 시간 동안 추가적인 요청이 발생하면 새로운 TCP 연결을 생성하지 않고 기존 연결을 재사용 한다. 
upstream docker-nginx {
    server nginx:80;
}

upstream docker-apache {
    server apache:80;
}
  • docker-compose를 사용하면 같은 네트워크 안에서 서비스 명으로 각각의 서비스를 호출할 수 있는데 그것에 대한 정보이다. docker-nginx라는 이름은 아무렇게나 지을 수 있다. 저렇게 꼭 지어야 하는 게 아니다. 그래서 docker-nginx라는 키워드를 사용하면 `nginx`라는 docker-compose로 띄운 서비스의 80포트로 요청을 전달한다.
  • docker-apache도 마찬가지다. 이름은 아무렇게나 지어도 된다. 그리고 이 docker-apache라는 키워드를 사용하면, `apache`라는 docker-compose로 띄운 서비스의 80포트로 요청을 전달한다.
  • 쉽게 말해 그냥, docker-nginx, docker-composealias같은 것이다.
server {
    listen 8080;

    location / {
        proxy_pass         http://docker-nginx;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}

server {
    listen 8081;

    location / {
        proxy_pass         http://docker-apache;
        proxy_redirect     off;
        proxy_set_header   Host $host;
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Host $server_name;
    }
}
  • 저번 포스팅에서도 본 적이 있는 server 블록이다.
  • 첫번째 server 블록 (8080 포트)
    • listen 8080 nginx가 8080번 포트로 들어오는 요청을 수신하도록 설정했다.
    • location / {...} 로 루트 경로(/)로 들어오는 모든 요청에 대한 설정을 했다.
    • proxy_pass http://docker-nginx; 모든 요청을 docker-nginx로 정의된 서버로 요청을 전달한다.
    • proxy_redirect off; 프록시 서버가 리다이렉션 응답을 받을 때, 리다이렉션 URL을 변경하지 않도록 설정한다.
    • proxy_set_header Host $host; 클라이언트가 보낸 호스트 헤더를 그대로 서버에 전달한다.
    • proxy_set_header X-Real-IP $remote_addr; 클라이언트의 실제 IP 주소를 X-Real-IP 헤더로 전달한다. 
    • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 클라이언트의 원래 IP 주소와 프록시를 거친 IP 주소들을 X-Forwarded-For 헤더로 전달한다.(그러니까 프록시에 또 프록시를 거칠수도 있으니까 말이다)
    • proxy_set_header X-Forwarded-Host $server_name; 서버의 이름을 X-Forwarded-Host 헤더로 전달한다.
  • 두번째 server 블록 (8081 포트)
    • listen 8081 nginx가 8081번 포트로 들어오는 요청을 수신하도록 설정했다.
    • location / {...} 로 루트 경로(/)로 들어오는 모든 요청에 대한 설정을 했다.
    • proxy_pass http://docker-apache; 모든 요청을 docker-apache로 정의된 서버로 요청을 전달한다.

 

이렇게 설정된 nginx 설정 파일이다. 이 파일을 nginx proxy가 사용할 것이다. 이제 docker-compose를 띄워보자.

#docker-compose.yml 파일이 있는 경로에서

docker-compose up -d

 

 

잘 띄워졌으면 다음 경로에 각각 요청해보자.

 

 

 

우리가 매핑한 정보대로 8080은 nginx 서버를, 8081은 apache 서버를 띄워주고 있다. 이게 nginx reverse proxy를 띄우고 각 서비스를 포트로 구분해서 포워딩하는 내용이다.

 

docker-compose Nginx proxy 띄우기 (경로로 서비스 구분)

이번엔 포트말고 경로로 두 서비스를 구분해보자.

docker-compose.yml

version: "3"

services:
    nginxproxy:
        image: nginx:1.18.0
        ports:
            - "80:80"
        restart: always
        volumes:
            - "./nginx/nginx.conf:/etc/nginx/nginx.conf"

    nginx:
        depends_on:
            - nginxproxy
        image: nginx:1.18.0
        restart: always

    apache:
        depends_on:
            - nginxproxy
        image: httpd:2.4.46
        restart: always
  • 이번엔 proxy 역할을 하는 nginx의 포트를 80만 열어두었다.
  • 마찬가지로 nginx.conf 파일을 그대로 컨테이너에 옮겨주는데 해당 설정 파일은 아래와 같다.
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;
    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;

    upstream docker-nginx {
        server nginx:80;
    }

    upstream docker-apache {
        server apache:80;
    }

    server {
        listen 80;

        location /nginx/ {
            proxy_pass         http://docker-nginx;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }

        location /apache/ {
            proxy_pass         http://docker-apache;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }

}
  • 이번엔 서버 블록이 하나만 있고 80 포트로 listen하고 있다.
  • 그리고 location으로 경로를 `/nginx/`, `/apache/` 로 구분지었다.

이 상태로 docker-compose를 띄워보자.

# docker-compose.yml 파일이 있는 경로에서

docker-compose up -d

 

잘 띄워졌다. 그럼 한번 브라우저에서 접속해볼까?

 

어라? nginx가 띄워지긴 한 모양인데 404가 떴다. 즉, 우리가 원하는 파일을 못 찾은것이다. 왜 그럴까?

우선 접속 경로는 `/nginx/`이다. 그럼 nginx 프록시 서버 설정에 의해서 docker-nginx가 지정한 nginx 서버로 요청을 전달할 것이다. 근데, 이 전달받은 서버는 nginx이다. 즉, 이 내부에 있는 nginx 서버는 어떤 경로를 받냐면 `/nginx/`를 그대로 받는데 이 내부에 있는 nginx 서버는 해당 경로를 모른다. 왜? 우리가 설정한 적이 없으니까! 

 

무슨 말인지 말로만 하면 이해가 안가니까, 내부 nginx 컨테이너로 들어가보자.

들어가기 위해 내부 nginx 컨테이너 이름을 확인해보자. 확인했으면 다음 명령어를 실행한다.

 

docker exec -it 04_nginx_proxy_path-nginx-1 /bin/bash

 

들어왔으면 설정 파일 위치를 찾자.

$ find -name nginx.conf
./etc/nginx/nginx.conf

 

위 예시처럼 찾았으면 해당 파일을 열어보면, 아래와 같이 http 블록안에 server 블록이 있지 않고 include로 나뉘어져 있다.

 

그럼 저 경로로 가서 어떤 파일이 있는지 확인을 해보면, default.conf 파일이 존재한다.

 

이 파일을 열어보면, 아래와 같이 80으로 listen하고 있는 서버가 하나 있지만, 이 서버의 location 정보에 /nginx/는 없다.

 

그렇다면, 모든 요청에 대해 location / {...} 가 처리하게 되고 이 녀석의 root 경로는 `/usr/share/nginx/html`이다.

그럼 외부에서 `/nginx/` 이런 경로로 요청이 들어오면 /usr/share/nginx/html/nginx/index.html 파일을 찾을것이다. 

왜냐하면 `/nginx/`는 마지막에 `/`만 붙고 다른 URI가 안 붙었으니 디렉토리를 찾고 그 안에서 index로 설정한 index.html이나 index.htm 파일을 찾을 것이니까. 

 

그래서 /usr/share/nginx/html 경로에 nginx 폴더를 일단 만들어야 한다.

# /usr/share/nginx/html 경로에서

mkdir nginx

 

그리고 이 폴더안에 /usr/share/nginx/html 경로에 있던 index.html 파일을 복사해서 넣어두자.

그러고 다시 브라우저에서 접속해보면, 아래와 같이 잘 나올것이다.

 

apache도 같은 이유로 제대로 나오지는 않을 것이다. 이왕 한 김에 apache도 제대로 나오도록 작업해보자.

우선 컨테이너 내부로 들어와서, 아파치의 설정 파일 경로는 여기에 있다.

/usr/local/apache2/conf

 

이 경로 안에 `httpd.conf` 파일이 설정 파일이다. 이 설정 파일을 열어보면,

위 사진과 같이 root 경로는 `/usr/local/apache2`이다. 

여기로 가보면, 실제 파일을 두는 경로는 `/usr/local/apache2/htdocs` 이곳이다.

 

이 경로에서 apache라는 폴더를 만들고 index.html 파일을 복사하자.

그러고 나서 브라우저에서 `/apache/`로 접속해보면, 잘 나온다.

 

근데, 솔직히 이 작업은 너무 귀찮다. 왜냐하면, 일단 위에서 했던 작업들이 필요하기 때문이다. 

Nginx 프록시 서버에서 경로 설정으로 location /nginx/ {...} 이렇게 한 순간 요청을 할 때, `/nginx/`이렇게 요청을 할 것이고, 그럼 그 요청 경로가 그대로 내부 nginx 서버에 전달될텐데 지금처럼 내부 nginx 서버에는 이 경로에 대해 정의한 것이 없으니 위처럼 작업이 필요해진다. 

 

이럴때 만약 `http://localhost/nginx` 이렇게 요청한 것을 프록시 서버가 전달할 때는 `http://localhost`로 바꿔주면 얼마나 좋을까? 그럼 내부 nginx 서버에서는 따로 경로에 대한 작업을 할 필요가 없어질 테니 말이다.

 

rewrite 옵션

위의 문제를 이렇게 해결할 수 있다.

location /nginx/ {
    rewrite ^/nginx(.*)$ $1 break;
    proxy_pass         http://docker-nginx;
    proxy_redirect     off;
    proxy_set_header   Host $host;
    proxy_set_header   X-Real-IP $remote_addr;
    proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header   X-Forwarded-Host $server_name;
}
  • 위 코드를 보면, rewrite 라는 옵션이 추가됐음을 확인할 수 있다. 그리고 정규 표현식이 나오고 뭐 등등 복잡하다.

표현식은 이렇다.

rewrite regex URL [flag]
  • regex: 매칭되는 URL 패턴을 설정한다. 
  • URL: 변경할 URL 기재
  • flag: 추가적인 옵션을 기재, 여기서는 break를 사용했는데 이 break는 어떤거냐면, 여러 개의 location이 있을 때, 변경된 URL이 그 여러개의 location 중 우연히 매칭이 될 수도 있기 때문에 break를 사용해서 변경된 URL은 다시 다른 location 설정에 따르지 않고 현재의 location 설정에만 적용한다는 의미이다.

그럼 저 `^/nginx(.*)$ $1`이 의미하는 건 무엇이냐면,

  • ^: 시작을 나타낸다.
  • /nginx: 시작한 후 나오는 문자
  • (.*): 임의의 문자열을 나타낸다. 
    • `.`: 임의의 한 문자를 나타낸다. (만약, 진짜 `.`을 의미하게 하고 싶으면 `\.` 이렇게 역슬래시를 붙여준다.)
    • `*`: 0회 이상의 문자를 나타낸다.
  • $: 문자 끝을 나타낸다.
  • $1: (.*)을 의미한다. 즉, ()로 묶인 부분을 $1로 받아올 수 있다.
    • 그래서 만약, `^/nginx(.*)/abc/(.*)$ $1` 이렇게 되어 있으면, 이제 $1, $2로 저 두개의 괄호 묶음을 가져올 수도 있다.

그럼 만약, `localhost/nginx/`로 요청이 들어오면, `localhost/`로 변경이 되는 것이다.

그래서 이 옵션을 적용한 파일은 이렇게 생겼다. 다른 것 없이 그냥 rewrite 더해주면 된다.

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;
    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;

    upstream docker-nginx {
        server nginx:80;
    }

    upstream docker-apache {
        server apache:80;
    }

    server {
        listen 80;

        location /nginx/ {
            rewrite            ^/nginx(.*)$ $1 break;
            proxy_pass         http://docker-nginx;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }

        location /apache/ {
            rewrite 	       ^/apache(.*)$ $1 break;
            proxy_pass         http://docker-apache;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Host $server_name;
        }
    }

}

 

이대로 docker-compose를 다시 실행해서 확인해보자!

결과는 동일하게 잘 나오지만, 난 내부 nginx 서버에 어떠한 경로도 적용해주지 않았다. 즉, 저 rewrite 옵션으로 인해 내부 nginx에 요청을 전달할 때는 `http://localhost`로 그냥 간 것이다.

 

그리고 좋은 사이트 하나가 있다.

 

https://nginx.viraptor.info/

 

nginx.viraptor.info

이 사이트는 뭐냐면, 이제 저렇게 여러 location이 막 있을 때, 어떤 location에 현재 요청이 걸리는지 헷갈릴 때가 언젠가 생긴다. 그럴때 그걸 알려주는 것이다.

 

 

추가적인 내용들

사실 nginx 설정이란 게 굉장히 다양하고 상황에 따라 달라지기 때문에 그때그때 필요한 것들을 찾아가야 한다. 그럼에도 불구하고, 자주 사용되는 것들은 정리해보려고 한다. 

 

에러 페이지 설정

error_page 403 404 405 406 411 497 500 501 502 503 504 505 /error.html;
location = /error.html {
	root /usr/share/nginx/html;
}
  • error_page 지시어는 HTTP 상태 코드가 403, 404, ... 505 인 경우 `/error.html` 경로로 리다이렉션 하라는 의미이다. 즉, 여기서는 `/error.html` 페이지를 클라이언트에게 보여주겠다는 의미가 된다.
  • location = /error.html { ... } 은 경로가 정확하게(=) `/error.html` 인 경우에 이 정의를 따른다.

 

정규 표현식을 사용하는 location

location ~ \.php$ {
	...
}
  • `~` 표시는 정규 표현식을 사용하겠다는 의미가 된다. 그래서 \.php$ `\.`가 정확히 딱 `.`을 의미하고 php가 나오고 $는 끝을 의미하니까 경로가 .php로 끝나는 경우를 의미한다.

 

캐시 설정

location ~* \.(ico|css|js|glf|jpe?g|png)$ {
    expires max;
    add_header Pragma public;
    add_header Cache-Control "public, must-revalidate, proxy-revalidate";
}
  • 정적 파일(CSS, JS, 이미지, ico)은 변경될 가능성이 극히 드물다. 그런데 그런 파일들을 요청이 들어올때마다 HTTP 요청 응답으로 처리하면 속도가 느리니 캐쉬 설정을 하는 것이다.
  • `~*`: 의미하는건 정규표현식은 정규표현식인데 대소문자를 구분하지 않겠다는 의미이다.
  • `\.(ico|css|js|glf|jpe?g|png)$`: 의미하는 건 .ico 또는 .css 또는 .js 또는 .glf 또는 .jpeg 또는 .jpg 또는 .png 대상을 의미한다. 저기 jpe?g는 e가 있을수도 없을수도 있다는 의미이다.
728x90
반응형
LIST

'Nginx' 카테고리의 다른 글

Nginx 기본 이해 (구동 방식, 설정 파일)  (0) 2024.09.19
728x90
반응형
SMALL

참고자료

 

풀스택을 위한 도커와 최신 서버 기술(리눅스, nginx, AWS, HTTPS, flask 배포) [풀스택 Part3] 강의 | 잔재

잔재미코딩 DaveLee | 본 강의는 풀스택 강의 시리즈 Part3 강의로 최신 서버 기술과 도커 기술을 탄탄하게 익히는 강의입니다. 본 강의는 실질적으로 도커를 내 기술 스택에 포함시킬 수 있도록, 도

www.inflearn.com

 

웹 서버는 HTTP 요청을 받아서 해당 요청에 대한 응답을 해주는 프로그램이다. 대표적인 웹 서버로 Apache, Nginx가 있는데 요즘 점점 더 뜨고 있는 웹 서버가 Nginx인것 같아 공부해 보려고 한다. 웹 서버는 프로그램을 서버상에 설치해서, 특정 HTTP 요청에 따라 서비스를 제공해주는 방식으로 구현된다.

 

NginxApache보다 점점 추세가 좋아질까? 난 아래 구동 방식의 차이에서 나온다고 생각한다.

Apache 구동 방식

Prefork MPM(Multi Processing Module) 방식

→ HTTP 요청이 올 때마다, 프로세스를 복제하여 각각 별도 프로세스에서 해당 HTTP 요청 처리

 

Worker MPM(Multi Processing Module) 방식

→ 하나의 HTTP 연결 후, 여러 요청을 처리하기 위해 복제된 프로세스 내에서 여러 쓰레드를 생성하여, 여러 HTTP 요청을 처리하는 방식

 

그러니까 결론은, HTTP 요청이 많아지면 많아질수록 프로세스도 여러개 만들어지고 프로세스 내 쓰레드도 여러개가 만들어진다.

Nginx 구동 방식

Event Driven 방식

  • 하나의 프로세스로 동작하며, HTTP 요청을 event로 비동기식으로 처리한다.
  • 대부분의 HTTP 응답은 결국 HTML 파일을 제공하는 것이므로, IO 작업이다.
  • 따라서, IO 작업으로 event를 포워딩하고, 요청 순이 아닌 요청 작업이 끝난 순으로 처리한다.

이렇게 되면, HTTP 요청마다 프로세스든 쓰레드든 생성이 필요없으니 시스템 자원 관리에 장점이 있다.

보통 접속자가 많을 때, 시스템 자원 관리 효율성 때문에 Nginx가 이론적으로는 성능이 좋을 수 있는데, 어떤 요청이고 그 요청이 어떤 작업을 하느냐에 따라 성능의 차이는 천차만별이라 꼭 그렇지도 않다. 그리고 결론적으로 웹 서버는 다양한 추가 기능과 함께 동작한다. 예를 들면, 플러그인의 작업이라든가 해서, 종합적인 성능에는 큰 차이를 보이지 않는다. 

그러나, 개인적인 생각으로는, 요즘 같이 대규모 서비스로 대고객의 요청을 받아들이는 세상에서 프로세스나 쓰레드를 지속적으로 만들어내서 요청을 처리하는 방식은 아무래도 오버헤드가 생길 수 밖에 없다고 생각한다. 그래서 Nginx가 요즘 더 뜨는 추세가 아닌가 싶다.

 

간단하게 ApacheNginx의 구동방식을 이해해 보았다. 이제 Nginx를 직접 설정해보자.

 

Nginx 설치

우선, Linux 환경안에서 시작해보자. 그러기 위해 도커를 사용한다.

docker run -dit -p 80:8080 --name myos ubuntu:20.04
docker exec -it myos /bin/bash
  • 여기까지 진행하면 해당 컨테이너의 쉘로 들어오게 된다. 이 안에서 다음 명령어를 실행하자.

 

apt-get update
apt-get install nginx
apt-get install vim

 

  • Nginx를 설치할 때 특정 버전을 명시하고 싶으면 nginx=XXX로 설치하면 된다. 
  • 설정 파일을 다루기 위해 vim도 설치해주자.

 

만약, 특정 버전이 어떻게 되는지 궁금하면 다음 명령어를 입력한다.

apt list -a nginx

 

아무튼 nginx 설치를 하면 다음과 같이 지역을 고르라고 나온다. Asia/Seoul로 하면 된다.

 

Nginx 설정 파일

nginx의 기본 설정 파일은 `nginx.conf` 파일이다. 이 파일의 경로를 찾기 위해 다음 명령어를 입력하자.

find -name nginx.conf

 

나의 경우 이 경로 `./etc/nginx/nginx.conf`에 있다. 이 파일을 vim으로 열어보자.

vi ./etc/nginx/nginx.conf

 

다음과 같이 보여질 것이다.

  • 상단에 `user`는 이 웹 서버를 실행할 수 있는 권한이 있는 사람을 의미한다. 그룹이 될 수도 있고 개인이 될 수도 있다. 굳이 건드리지 말자.
  • 상단에 `worker_processes`nginx의 워커 프로세스 수를 설정한다. 요청을 처리하는 nginx의 개별 프로세스 수를 나타내며, auto로 지정되어 있으면 nginx는 시스템의 CPU 코어수에 맞춰 자동으로 적절한 수의 프로세스를 생성한다. 이 부분도 굳이 건드리지 말자.  
  • 상단에 `pid`nginx의 메인 프로세스 ID를 저장하는 파일의 경로를 의미한다. 이 nginx가 실행될 때 해당 파일에 자신의 PID를 기록한다.
  • 상단에 `include`는 지정된 경로에 있는 파일을 이 설정 파일에 포함시킨다. 그래서 여러개의 설정 파일을 쉽게 관리할 수 있게 해준다.
"어? nginx는 이벤트 비동기 처리 방식으로 요청별로 프로세스가 늘어나는 방식이 아니라면서요?" → 아주 좋은 질문이다. 말처럼 nginx는 이벤트 비동기 처리 방식으로 요청을 처리하지만, 하나의 프로세스만으로 모든 요청을 효과적으로 처리할 수는 없다. 생각을 해보라. 프로세스가 하나만 도는데 요청이 만건이다. 그럼 프로세스는 하나라 CPU는 남아도는데 하나의 프로세스가 모든 요청을 처리해야 하면 정말 비효율적으로 요청을 처리하는 것이다.  그래서, 워커 프로세스를 CPU 코어 수에 맞춰(예를 들면 4개) 만들어 두면 4개의 프로세스가 무수히 들어오는 요청에 대해 이벤트 비동기 처리를 하게 되고 그러면 하나의 프로세스보다는 훨씬 더 효율적으로 처리할 수 있게 된다. 그래서 auto로 지정하면 CPU 코어수에 맞춰 적절하게 프로세스를 생성한다. 

 

그 다음 그 아래 있는 http 블록이 가장 중요하고, 이 블록이 전체 웹 서버 기본 설정 블록이다.

http 블록 하단에 보면 여기도 `include`가 있다. 일반적으로 nginx 웹 서버를 하나 띄우고 얘가 관리하는 서비스가 하나가 아니라 여러개이다. 그리고 그 각각의 서비스는 별도의 설정 파일로 관리하고 저렇게 `include`로 해당 설정 파일을 포함시킨다. 

 

그래서 일반적으로, `/etc/nginx/conf.d/xxxsite.conf` 이렇게 사이트(서비스)별로 설정파일을 따로 분리하여 설정한 후 저렇게 포함시킨다.

 

그리고 `/etc/nginx/site-enabled/*``include`로 포함시켰는데 이 역시 해당 경로의 모든 것들을 다 포함시킨다는 의미가 된다. 한번 이 두개의 경로를 직접 들어가보자.

 

`/etc/nginx/conf.d/` 이 경로에는 아무것도 없다. 당연한 게 일단 우리가 만든 서비스도 없고 뭐 아무것도 없기 때문에 없는게 당연하다. 그리고 `/etc/nginx/site-enabled/` 이 경로에는 다음과 같이 default가 하나 있다.

이게 이제 nginx 설치하고 80으로 진입하면 기본으로 보여주는 사이트이다. 한번 저 default 파일을 열어보면,

  • 이렇게 보여지는데, 이 server 블록이 결국 http 블록안에 포함되어서 여러 서비스(서버)가 설정될 수 있게 된다. (왜냐? 이 경로의 모든 파일을 다 include 시킨 것을 위에서 확인했으니까)
  • 그래서 http { ... server {} server {} server {} ... } 뭐 이런 모양으로 이제 구성이 된다. 

그리고 이 sites-enabled 경로에서 `ls -al` 명령어를 쳐보면, 다음과 같이 default 파일은 심볼릭 링크가 걸려있음을 알 수 있다.

그러니까 실질적으로 저 파일이 위치하는 경로는 이 sites-enabled가 아니라, sites-available에 있다. 한번 들어가보면 다음과 같다.

"근데, 왜? 왜 이렇게 해놓은거지 구조를?"

의아하다. 실질적으로 default 파일의 경로는 sites-available에 있으면 nginx.conf 파일에서 includesites-available로 하면 될 것을 굳이 sites-enabled로 해놓고 default 파일을 심볼릭 링크를 걸어 둔 이유가 무엇일까? 

 

추측컨데, 개발자의 의도는 'available'(이용가능한) 사이트와 'enabled'(현재 활성화) 된 사이트를 구분하고 싶었던 것 아닐까? 그래서 "서비스로 띄울 사이트들을 일단은 available에 올려두고, 이 중에 enabled 시킨 것들만 서비스하게 하자." 이런 의도가 있지 않았나 싶다. 

 

server 블록

이제 default 파일에서 봤던 server 블록 내부를 이해해보자. 이 블록 전체는 다음과 같이 생겼다.

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        # SSL configuration
        #
        # listen 443 ssl default_server;
        # listen [::]:443 ssl default_server;
        #
        # Note: You should disable gzip for SSL traffic.
        # See: https://bugs.debian.org/773332
        #
        # Read up on ssl_ciphers to ensure a secure configuration.
        # See: https://bugs.debian.org/765782
        #
        # Self signed certs generated by the ssl-cert package
        # Don't use them in a production server!
        #
        # include snippets/snakeoil.conf;

        root /var/www/html;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }

        # pass PHP scripts to FastCGI server
        #
        #location ~ \.php$ {
        #       include snippets/fastcgi-php.conf;
        #
        #       # With php-fpm (or other unix sockets):
        #       fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
        #       # With php-cgi (or other tcp sockets):
        #       fastcgi_pass 127.0.0.1:9000;
        #}

        # deny access to .htaccess files, if Apache's document root
        # concurs with nginx's one
        #
        #location ~ /\.ht {
        #       deny all;
        #}
}

 

가장 첫번째로 보이는 이 부분이다.

listen 80 default_server;
listen [::]:80 default_server;
  • 어떤 포트를 잡을지를 의미한다. 이 서버는 80포트와 매핑한다. 이 포트를 변경하면 해당 포트로 이 서버를 매핑한다.
  • nginxhttp 블록안에 여러개의 server 블록을 가진다고 했다. default_server; 이 부분은 해당 포트(80)으로 들어온 요청이 특정 서버 블록과 매칭되지 않을 때 기본적으로 처리할 서버임을 의미한다. 예를 들어 도메인이나 호스트 이름이 매칭되지 않는 경우 nginx는 이 default_server로 정의된 서버 블록을 사용해서 요청을 처리하게 된다.
  • [::]:80 이 부분은 IPv6 주소를 의미한다.
root /var/www/html;

# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
  • `root`는 웹 서버에서 요청된 파일을 찾기 위한 기본 디렉토리를 설정하는 지시어다. `/var/www/html`은 이 서버 블록에 대한 웹 컨텐츠가 저장된 디렉토리 경로이다. 즉, 클라이언트가 웹 서버에 요청을 보내면, nginx는 해당 파일을 /var/www/html 디렉토리에서 찾는다. 
  • 예를 들어, 클라이언트가 http://example.com/page.html을 요청하면, nginx/var/www/html/page.html을 찾는다.
  • 이 경로는 웹 서버의 기본 웹 컨텐츠 폴더로 많이 사용된다. 웹 사이트의 HTML, 이미지, CSS 파일 등이 이 디렉토리에 위치하게 된다.
  • `index`nginx가 디렉토리 요청을 처리할 때 사용할 기본 인덱스 파일을 지정하는 지시어다.
  • 예를 들어, 클라이언트가 http://example.com/처럼 디렉토리만 요청했을 때, nginx는 자동으로 해당 디렉토리에서 설정된 인덱스 파일을 찾아서 제공한다. 
  • `index` 지시어 뒤에 나열된 파일들은 기본 인덱스 파일의 우선순위를 나타낸다. 왼쪽에서부터 가장 먼저 찾는 파일을 반환한다. 
  • 나의 경우 해당 경로에 다음 파일이 있다.

server_name _;

location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
}
  • `server_name _;`nginx가 이 서버 블록에서 처리할 도메인 이름이나 호스트 이름을 지정하는 것이다. 여기서 `_`는 특별한 의미를 가지고 있지 않으며, 일반적으로 매칭되지 않은 모든 요청에 대해 이 서버 블록이 처리되도록 설정할 때 사용된다.
  • 그러니까 만약, `server_name cwchoiit.org www.cwchoiit.org;` 이런식으로 되어 있으면 이 서버 블록은 이 도메인으로 들어오는 요청만 처리하는 것이다. 
  • location 블록은 nginx가 특정 요청 경로(URI)에 대해 어떻게 처리할지를 정의하는 부분이다. 예를 들어 아래와 같이 작성할 수가 있다.
location /blog {
	root /var/www;
}
location /flask {
	root /var/www;
}
location / {
	try_files $uri $uri/ =404;
}
  • 그럼 요청이 `/blog/xxx`로 들어오면 저 location /blog {...} 로 처리하는 것이고, `/flask/xxx`로 들어오면 저 location /flask {...}로 처리하는 것이다.
  • try_files $uri $uri/ =404; 이 부분은 try_files부터 살펴보면, 이 try_filesnginx가 요청된 리소스를 찾을 때 여러 경로를 시도하도록 지시하는 명령어다. 
  • $uri, $uri/ 는 각각 요청된 URI와 그 디렉토리 경로를 의미한다. 이 지시어는 아래 순서로 파일을 찾는다.
    • $uri: 요청된 URI에 해당하는 파일이 있는지 확인한다. 예를 들어, 클라이언트가 /about.html을 요청했을 때, /var/www/html/about.html 파일이 있는지 확인한다. 
    • $uri/: 요청된 URI가 디렉토리일 경우, 해당 디렉토리가 있는지 확인한다. 예를 들어, /about/으로 요청이 들어오면 /var/www/html/about/ 디렉토리가 있는지 확인한다.
    • =404: 만약, 위 두가지 방법으로도 파일이나 디렉토리를 찾을 수 없다면, 404 오류를 반환한다. 

 

Nginx server 블록 수정 후 재실행

맨 처음에 도커 컨테이너 실행할 때 매핑한 포트 기억하는가? 80:8080으로 매핑했다. 즉, 외부에서 80으로 들어오면 컨테이너는 8080에 매칭시킨다는 의미가 된다. 그래서 이 default 파일의 포트를 8080으로 수정해보자.

server {
        listen 8080 default_server;
        listen [::]:8080 default_server;
        
        ...
        
}

이렇게 수정을 하자. 즉, 이제 이 default server 블록은 8080 포트를 리슨한다. 그리고 이 default 파일을 http 블록에서 include하고 있기 때문에 웹 서버가 서비스하고 있는 상태이다. 이렇게 설정 파일을 수정했으면 nginx를 재실행해야 한다.

service nginx restart

 

이 명령어를 실행해서 재실행하고 나서, 내 브라우저를 열어서 그냥 `localhost`로 접속해보자.

그럼 이렇게 nginx 기본 페이지가 보여진다! 이 파일이 바로 default가 보여주는 서비스인 것이다. 그리고 어떠한 URI도 붙이지 않았으니, root 경로의 index 파일을 찾을 것이고 그 index 파일은 다음과 같이 생겼다.

 

index.nginx-debian.html

이 HTML이 보여진 것이다. 이 HTML을 수정해보면? 그대로 적용될 것이다.

그럼 이번에는 location을 다음과 같이 수정해보자.

default

 location /blog {
        root /var/www;
}

location /flask {
        root /var/www;
}

location / {
        # First attempt to serve request as file, then
        # as directory, then fall back to displaying a 404.
        try_files $uri $uri/ =404;
}
  • 이제 외부에서 요청을 했을 때 `/blog/a.html`로 요청을 하면 저 location /blog {...} 정의대로 처리가 된다. 
    • /var/www/blog/a.html 파일을 찾을것이다.
  • 이제 외부에서 요청을 했을 때 `/flask/b.html`로 요청을 하면 저 location /flask {...} 정의대로 처리가 된다.
    • /var/www/flask/b.html 파일을 찾을 것이다.
  • 만약, `/bbb/c.html`로 호출하면? 그렇다. 아무것도 매칭이 안됐으니 location / {...} 정의대로 처리가 된다.
    • location / {...} 에는 root가 정의되지 않았으니 글로벌로 정의된 위쪽의 root /var/www/html을 사용할 것이고 찾는 경로는 /var/www/html/bbb/c.html을 찾을것이다.

 

이렇게 설정을 한 후 `/var/www` 경로에서 blog, flask 폴더를 하나씩 만들자.

# /var/www

mkdir blog
mkdir flask

 

그리고 이 각각의 폴더에 `/var/www/html` 경로에 있는 index.nginx-debian.html 파일을 복사해서 index.html 파일로 만들자.

# /var/www/html

cp index.nginx-debian.html ../blog/index.html
cp index.nginx-debian.html ../flask/index.html

 

그리고 각각의 index.html 파일의 타이틀을 blog!!!, flask!!!로 변경하고 nginx를 재실행 해보자.

 

 

그럼 우선 `localhost`로 접속하면 다음과 같이 보일것이다.

 

이번엔 `localhost/flask`로 접속하면 이렇게 보일것이다.

왜냐? `/flask/`로 요청이 들어오면 location 설정값에 의해 /var/www/flask 디렉토리를 찾고, 그 이후에 붙은 URI가 없으니 index 파일을 찾을것이므로.

 

같은 이유로 `localhost/blog`로 접속하면 이렇게 보여질 것이다.

 

정리를 하자면

이렇게 기본적인 nginx의 구동 방식, 설정 파일에 대해 알아보았다.

728x90
반응형
LIST

'Nginx' 카테고리의 다른 글

Nginx - Reverse Proxy 설정  (2) 2024.09.19

+ Recent posts