Nginx 를 사용하는 이유와 사용 방법을 정리
Nginx 를 설명하기 위해 Apache 를 설명하는 것으로 시작할 예정이니
Apache, Nginx 사용 이유에 관심이 없으시다면 우측 제목을 통해 'nginx 사용법'으로 Go!
현재 웹 서버 시장 점유율 1위를 기록하는 아파치
nginx 를 설명하기 위해서 아파치가 뭔지 알고 있으면 이해에 큰 도움이 된다고 생각함
전반적으로 로직 자체가 간단하기 때문에 개발하기 용이함
또한 모듈을 만들어서 기능을 추가하기도 원활한 구조였기에 확장성까지 좋은 그 당시 최고의 구조였음
아파치의 점유율은 날이 갈수록 증가했고 결국 아파치 서버 구조가 가져다주는 문제인 C10K 를 직면하게 됨
동시 커넥션 수가 1만개가 되었을 때 더이상 아파치 서버가 커넥션을 형성하지 못하는 문제
Connection 10000 problem => C10K
nginx 는 c10k 의 동시 커넥션이라는 문제를 해결하기 위한 구조로 되어 있음
How? 어떻게 nginx는 수많은 커넥션을 유지할 수 있는 것인가?
nginx의 구조는 마스터 프로세스로부터 시작
마스터 프로세스는 nginx의 설정값을 읽고 워커 프로세스를 생성함
이 워커 프로세스가 실제로 요청을 받고 처리하는 프로세스임
워커 프로세스는 생성될 때 socket을 배정받게 되고 이 소켓에 요청이 들어오면 커넥션을 형성하고 요청을 처리하게 됨
커넥션은 아파치 서버와 동일하게 keep-alive 를 통해 유지시킬 수 있음
중요한 것은, 아파치 서버와는 다르게 이미 생성된 커넥션을 하나의 요청만 한정해서 사용하지 않는다는 것
유지 중인 커넥션에 아무 요청이 없다면 다른 요청들을 가져와서 처리함
이 말은 곧 쉬지 않고 동작한다는 것을 의미하며 아파치 서버와 비교했을 때 요청이 없으면 방치되던 프로세스와는 다르니 서버 자원을 효율적으로 사용하게 되고, 이는 곧 메모리를 절약하는 장점을 얻을 수 있음(실제로 벤치마킹 해보면 동일 커넥션 대비 nginx의 메모리 사용률이 apache에 비해 현저히 낮음을 확인할 수 있다고 한다!)
위에서 설명한 nginx가 커넥션을 생성하고 요청을 처리하는 일련의 과정들을 이벤트라고 명칭함
nginx의 이벤트는 OS 커널이 큐 기반의 형식으로 워커 프로세스에게 전달되는데, 이 이벤트들은 큐에서 비동기 방식으로 워커 프로세스가 앞단의 이벤트를 처리할 때 까지 기다림
워커 프로세스의 갯수는 여러개 일 수 있으며, 각 프로세스를 만들어 놓음으로써 하나의 cpu core가 담당해야 하는 일련의 context switching 작업들을 분할할 수 있음.
auto
를 통해 nginx가 자동으로 설정해주는데, cpu core 갯수만큼 설정하는 것이 일반적이고 고성능 퍼포먼스를 위해 커스텀도 가능함
이와 같은 구조는 아파치에 비해 동시 커넥션을 유지하기에 용이할 수 밖에 없음
정답이 없다
아파치 역시 시대의 흐름에 맞춰 구조를 개선해왔고(ex: MPM, ..) 그에 따라 다양한 OS에 안정적이고 수많은 모듈에 커뮤니티까지 막대한 아파치는 현재에도 웹 서버 점유율 1위를 차지하고 있음
회사는 단 하나의 서비스보다는 여러 서비스를 제공할 것이며, 각 서비스의 모델에 맞는 구조를 선택하면 됨
$ sudo service nginx start // 실행
$ sudo service nginx restart // 재실행
$ sudo service nginx stop // 정지
$ sudo service nginx status // 동작 상태
$ nginx -t // nginx 설정파일에 대한 문법 검사, nginx가 에러를 뿜을 때는 메세지를 정상적으로 출력하지 않기 때문에 이 명령어를 통해 어떤 부분에 오류가 있는지 확인할 수 있음
마스터 프로세스는 설정 파일을 읽고 워커 프로세스를 생성한다고 했었다.
이는 실제로 다음과 같이 확인 가능하다.
$ ps aux --forest | grep nginx
현재 정상적으로 nginx가 실행되고 있으며, 그에 따라 마스터 프로세스 밑에서 2개의 워커 프로세스가 동작하고 있음을 확인할 수 있다.
simple directive, block directive 로 나뉘며 다음과 같음
// simple directive
// name value; 의 형태
// 세미콜론 빼먹지 말 것
worker_process auto;
// block directive
// context {
// simple directive..
// context {
// ..
// }
// }
events {
worker_connection 768;
}
include
를 block directive 내에 사용해서 작성한 로직을 불러올 수 있음context {
...
include /etc/nginx/conf.d/*.conf;
}
ec2 ubuntu를 기반으로 작성
일단 nginx를 실행해보자
$ sudo service nginx start
그 후 잘 실행되고 있는지 체크해본다
$ sudo service nginx status
웹 서버 nginx을 실행했으니 curl
을 통해서 접속이 잘 되는지 체크해보자
curl 127.0.0.1
nginx을 설치할 때 기본 셋팅이 되어 있었다면 아마 welcome nginx... 하는 index.html을 반환했을 것이다.
하지만 curl: (7) Failed to connect to 127.0.0.1 port 80: Connection refused
와 같은 에러를 뿜는다면, 이는 server block directive의 listen 소켓을 설정하지 않았기 때문이다.
이전 버전의 nginx는 정확히는 모르겠지만 최신 버전의 nginx는 /etc/nginx/sites-available/default 파일과 /etc/nginx/sites-enabled/default 파일이 심볼릭 링크로 연결되어 있기 때문에 둘 중 하나를 수정해주면 nginx.conf 에서 include 로 가져와 사용할 수 있게 된다
nginx 의 기본 위치는 /etc/nginx
$ cd /etc/nginx
/etc/nginx$ sudo vi sites-enabled/default
다음과 같이 작성해본다
server {
listen 80; // default, nginx에서 설정한 어떤 포트로든 접근할 수 있고 ec2 같은 경우 포트 허용을 뚫어주면 실 ip로도 해당 포트에 접근할 수 있다.
}
nginx는 설정을 변경할 때마다 재실행 해줘야 한다
/etc/nginx$ sudo service nginx restart
다시 curl
을 통해 nginx index.html이 잘 가져와지는지 확인해보자
/etc/nginx$ curl 127.0.0.1
이렇게 index.html 을 잘 가져오고, ec2 의 ip로 집적 접속해보면 다음과 같은 화면을 볼 수 있다.
server_name 은 특정 ip를 입력하거나 하는 귀찮음을 막고자 로컬 도메인을 설정해줌으로써 편하게 접근하도록 하기 위해 사용한다. (alias 같은 느낌)
/etc/nginx$ sudo vi sites-enabled/default
에서 server_name을 추가해보자
server {
listen 80;
server_name temp.com;
}
curl
명령어를 통해 설정해둔 server_name 으로 접근해보자
/etc/nginx$ curl temp.com
문법에 오류가 없다면 nginx index.html이 잘 가져와질 것이다.
크롬과 같은 브라우저에서는 temp.com으로 접근할 수 없다는 것은 당연한 것.
location 은 요청을 어디로 라우팅할 지 결정하는 block directive 로 server block directive 내에 존재해야한다.
/ 와 /hello 로 접속하는 요청을 구분해서 처리해보자
/etc/nginx$ sudo vi sites-enabled/default
server {
listen 80;
server_name temp.com;
location / {
return 200 "This Page is Index.html\n";
}
location /hello {
return 200 "Hello world!!!\n";
}
}
재실행 후 curl
을 통해 /, /hello 로 접속해보자
nginx 는 요청에 대한 path를 prefix로 처리하기 때문에 /blabla로 요청해도 /에 대한 결과를 뿜을 것이다
엄격하게 path를 제어하고자 한다면 location = / { ...}
와 같이 설정해주면 된다
nginx는 말그대로 웹 서버이므로 정적파일을 처리할 수 있다
특정 사용자가 /static/image.jpg
로 요청해서 image.jpg 파일을 제공받는 상황을 가정해서 설정해보자
/etc/nginx$ sudo vi sites-enabled/default
server {
listen 80;
server_name temp.com;
location /static {
root /tmp;
}
}
사용자는 /static/image.jpg
의 /static
으로 정적 파일을 요청하도록 location을 설정해준다
그리고 location 내부의 root 는 /tmp
이라는 폴더에 대한 상위 prefix 위치값을 설정해준다
위의 경우 최종 경로는 /tmp/static
이라는 곳으로 접근하게 된다는 의미이다
실제로 저 경로를 만들고 정적파일 image.jpg를 제공할 수 있도록 예시 파일을 만들어보자
/etc/nginx$ cd /tmp
/tmp$ mkdir static
/tmp$ cd static
/tmp/static$ touch hello.txt
/tmp/static$ vi hello.txt
<- 아무거나 작성
nginx 을 재실행하고 ec2 ip를 통해 ip/static/hello.txt 로 접속하면 파일이 제공되는지 확인해보자
(물론 curl 을 통해서도 가능)
nginx가 제공하는 기능 중 하나로, 사용자가 어디로 접근하든지 간에 원하는 곳으로 위치시켜줄 수 있다. 이는 보안에도 유리하며 밸런싱을 통해 부하를 줄이는데에도 사용할 수 있다(로드밸런싱)
나는 ec2 에 react project를 clone 해 온 상태이니, 이 project 를 3000번 포트에서 실행시키고 특정 사용자가 요청했을 때 그 프로젝트를 서빙하는 것을 예시로 든다.
$ sudo vi /etc/nginx/sites-enabled/default
server {
listen 80;
...
location /project {
proxy_pass http://localhost:3000;
}
}
/project 로 접근하면 알아서 http://localhost:3000 으로 다이렉트되는 것을 확인할 수 있다.
(만약 이렇게 프로젝트를 실행해서 접근하는 경우 지금까지 설정해둔 sites-enabled/default 내에 참조가 중복되서 프로젝트에 접근할 수 없는 경우가 생길 수 있다. /tmp 의 static 폴더를 삭제하고 설정파일의 참조 중복이 발생되는 코드를 삭제해주신 후 다시 시도해보면 잘 접근됨을 확인할 수 있다)