nginx에 upstream을 사용하고 있는데, 에서 어느 시간부터 다음과 같은 에러가 선형적으로 늘어가기 시작했고 엄청난 양의 에러가 발생하기 시작했다.


no live upstreams while connecting to upstream, client: ip_address , server: example.com, request: "GET / HTTP/1.1", upstream: "http://example.com", host: "example.com", referrer: "http://example.com/mypages/"


upstream prematurely closed connection while reading response header from upstream, client: ip_address , server: example.com, request: "GET / HTTP/1.1", upstream: "http://example.com", host: "example.com", referrer: "http://example.com/mypages/"




문제 해결 방법을 차례로 테스트했다.


1. timeout (proxy_timeout 등등)

2. upstream keepalive 

3. backend resource thread pool


그러나 여전히 문제를 해결할 수 없었다.


2개의 backend를 보면 upstream을 보던 설정을 1개의 backend로 보게 하고 tcpdump를 뜨면서 에러가 날 때 어떠한 현상이 있는지 확인했다.


no live upstreams while connecting to   upstream, upstream prematurely closed connection while reading response header from upstream 에러 발생과  tcpdump 덤프와의 인과 관계가 없음을 확인했다.


이 문제는 nginx 내부의 문제로 생긴 것으로 생각하고 소스를 확인해봤다.



https://github.com/nginx/nginx/blob/master/src/stream/ngx_stream_proxy_module.c#L696


    if (rc == NGX_BUSY) {

        ngx_log_error(NGX_LOG_ERR, c->log, 0, "no live upstreams");

        ngx_stream_proxy_finalize(s, NGX_STREAM_BAD_GATEWAY);

        return;

    }



https://github.com/nginx/nginx/blob/beaaeb9f9e642d1d153ee65569d99499eef624e9/src/http/ngx_http_upstream.c#L3551



                if (upstream->read->eof) {

                    ngx_log_error(NGX_LOG_ERR, upstream->log, 0,

                                  "upstream prematurely closed connection");


                    ngx_http_upstream_finalize_request(r, u,

                                                       NGX_HTTP_BAD_GATEWAY);

                    return;

                }



connection 이슈인 것을 확인했다..


nginx 설정을 보니 아... 내가 못 보던 nginx 사용자 정의 모듈이 있었고 해당 모듈이 특정 서버를 바라보고 있었다.


해당 모듈의 통신이 upstream에 영향을 주는 것으로 판단하고 해당 모듈을 사용하지 않도록 하니..


더이상 에러는 발생되지 않았다..





Posted by '김용환'
,


nginx 설정에서 


nginx->java(tomcat, jetty, netty)로 사용하는 reverse proxy 구조에서는 Host 설정이 중요하지 않지만,


nginx->nginx 로 사용하는 reverse proxy 구조에서는 Host 설정이 중요하다. nginx에서는 Host 설정이 헤더로 들어오지 않으면 400 에러를 발생한다.(http://knight76.tistory.com/entry/nginx%EC%97%90-host-%ED%97%A4%EB%8D%94-%EC%97%86%EC%9D%B4-%EC%84%9C%EB%B9%84%EC%8A%A4-%EB%90%98%EA%B2%8C-%ED%95%98%EA%B8%B0)


예시


        location ^~ /plus-image {

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header Host plus.image.google.co.kr;

            proxy_pass http://plus.image.google.co.kr/meta/;

}



Posted by '김용환'
,



HTTP 1.1 스펙에 따르면 반드시 Host 헤더를 추가해야 한다고 한다.


https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html


14.23 Host

The Host request-header field specifies the Internet host and port number of the resource being requested, as obtained from the original URI given by the user or referring resource (generally an HTTP URL,

as described in section 3.2.2). The Host field value MUST represent the naming authority of the origin server or gateway given by the original URL. This allows the origin server or gateway to differentiate between internally-ambiguous URLs, such as the root "/" URL of a server for multiple host names on a single IP address.

       Host = "Host" ":" host [ ":" port ] ; Section 3.2.2

A "host" without any trailing port information implies the default port for the service requested (e.g., "80" for an HTTP URL). For example, a request on the origin server for <http://www.w3.org/pub/WWW/> would properly include:

       GET /pub/WWW/ HTTP/1.1
       Host: www.w3.org

A client MUST include a Host header field in all HTTP/1.1 request messages . If the requested URI does not include an Internet host name for the service being requested, then the Host header field MUST be given with an empty value. An HTTP/1.1 proxy MUST ensure that any request message it forwards does contain an appropriate Host header field that identifies the service being requested by the proxy. All Internet-based HTTP/1.1 servers MUST respond with a 400 (Bad Request) status code to any HTTP/1.1 request message which lacks a Host header field.


간단히 테스트 해본다.



// $ curl -v -I -o /dev/null  http://.....jpg  아래 커맨드와 동일하다. 알아서 host 헤더를 채운다.

$ curl -v -I -o /dev/null -H "host:plus.google.com" http://.....jpg

  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current

                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0*   Trying 1.1.1.1

* Connected to ...

> HEAD ....jpg HTTP/1.1

> host:plus.google.com

> User-Agent: curl/7.43.0

> Accept: */*

>

< HTTP/1.1 200 OK

< Server: openresty

< Date: Wed, 01 Feb 2017 08:41:06 GMT

< Content-Type: image/jpg

< Content-Length: 23357

< Connection: keep-alive

< X-Kakao-crc32: 75299291

< Expires: Thu, 31 Dec 2037 23:55:55 GMT

< Cache-Control: max-age=315360000

< Age: 601526

<

  0 23357    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

* Connection #0 to host plus.google.com left intact


하지만 host가 없으면 400 에러가 뜬다.


$ curl -v -I -o /dev/null -H "host:" http://.....jpg

* Connected to ...

> HEAD ....jpg HTTP/1.1

> host:

> User-Agent: curl/7.43.0

> Accept: */*

>

< HTTP/1.1 400 Bad Request





따라서 reverse proxy 설정할 때 종종 빠뜨릴 수 있는데. 


HTTP 1.1 서비스를 위해 proxy_set_header을 추가해야 한다. (이것 땜시 삽질해서 정리해둠)


proxy_set_header Host $host;



Posted by '김용환'
,




reverse proxy 기능을 사용하기 위해 location 블럭을 정규식으로 작성하고 proxy_pass를 사용할 때 no resolver defined to resolve 가 발생할 수 있다. 



location ~ "^/(case|meta)/(.*)$" {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host plus.google.com;
proxy_pass http://dn.google.com/image/$2${args};
}


2017/02/01 20:00:21 [error] 61700#0: *103 no resolver defined to resolve dn.google.com,



다음처럼 resolver 지시자에 사내 DNS를 추가하면 해당 이슈가 발생하지 않는다.



location ~ "^/(case|meta)/(.*)$" { resolver 1.2.3.4;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host plus.google.com;
proxy_pass http://dn.google.com/image/$2${args};
}








Posted by '김용환'
,



openresty에서 lua를 이용한 health check url을 만들었다. 


nginx는 파일 기반으로 되어 있고 문법이 그다지 친절하지 않아서, openresty의 lua를 이용하면 좀 shared memory를 공유하는 기법이 있다. 전역 변수를 lua_shared_dict을 사용하면 모든 worker에서 서로 공유할 수 있어 편리한다.


공유 dict에 값이 없으면 200으로 처리해 서비스되도록 한다. 그리고 공유 메모리(lua_shared_dict)의 크기는 작게 한다. 



http {

    lua_shared_dict dicts    16k;

    

    ....

     server {

        server_name _;


        listen 80;

        root /...


        location / {

            return 404;

        }


        location ~* \.(?:ico|css|js|gif|jpe?g|png|gif|eot|ttf|ott|woff2?|swf|svg|zip)$ {

            expires 1y;

        }


        location /health_off {

            default_type text/html;

            content_by_lua_block {

                local d = ngx.shared.dicts

                d:set("health", 400)

                ngx.say("health-off")

             }

             allow       192.168.0.0/16;

             allow       172.16.0.0/12;

             allow       127.0.0.1;

             deny        all;

         }


        location /health_on {

            default_type text/html;

            content_by_lua_block {

                local d = ngx.shared.dicts

                d:set("health", 200)

                ngx.say("health-on")

            }

            allow       192.168.0.0/16;

            allow       172.16.0.0/12;

            allow       127.0.0.1;

            deny        all;

        }


        location /health_check.html {

            access_log  off;

            allow       all;


            default_type text/html;

            access_by_lua_block {

                local dogs = ngx.shared.dicts

                local status = tonumber(dogs:get("health")) or 200

                if status == 400 then

                        return ngx.exit(400)

                end

                return ngx.exit(200)

            }

        }

...

}




결과는 다음과 같다. 


$ curl -i http://localhost/health_check.html

200

$ curl -i http://localhost/health_off

$ curl -i http://localhost/health_check.html

400

$ curl -i http://localhost/health_on

$ curl -i http://localhost/health_check.html

200



**************


진짜 주의할 점은 ngx.exit 사용시 ngx.say를 함께 사용해서는 안된다. ngx.exit 앞에 ngx.say를 사용하면 exit 값이 나타나지 않고 200으로만 리턴한다. ngx.exit를 사용할 때는 ngx.say를 사용하지 않는다. 



Posted by '김용환'
,



1. 


한 라인 주석은 --

멀티 라인 주석은 --[[  코드  ]]--

동일(eual) ==

동일하지 않음(not equal) ~=

if문에 ()를 사용하지 않는다





2. 


lua블럭을 쓸 때는 비슷한 내용이 있다. 


content_by_lua_block {

}

 

content_by_lua '

';



general하게 쓸 때는 access_by_lua_block 또는 access_by_luay를 사용한다. 





3. nginx 의 변수를 lua내에서 사용 가능하다.


local page = ngx.var.arg_page

local authorization = ngx.var.http_authorization

local host = ngx.var.host





공부할 만한 자료 . 


Using ngx_lua / lua-nginx-module in pixiv from Shunsuke Michii


Posted by '김용환'
,

openresty 1.11.2 설치

nginx 2017. 1. 19. 19:03


openresty 를 설치할 때 centos 하위 버전에서는 설치가 안될 수 있으니. requirement를 살펴보면 좋을 듯 하다.

예를 들어 luajit을 사용할 수 있도록 PATH에 넣어달라는 문구를 받을 수 있다.



$ ./configure -j2 --with-pcre=/home/www/pcre-8.39

platform: linux (linux)

you need to have ldconfig in your PATH env when enabling luajit.


https://openresty.org/en/installation.html

You should have perl 5.6.1+libreadlinelibpcrelibssl installed into your system. For Linux, you should also ensure that ldconfig is in your PATH environment.


$ ls  /sbin/ldconfig

/sbin/ldconfig


$ vi ~/.bashrc

export PATH=$PATH:/sbin


$ source ~/.bashrc






http://openresty.org/en/getting-started.html에 따라 필수 유틸리티와 openresty를 설치한다.




./configure --prefix=/usr/local/nginx  --with-pcre=/usr/local/src/pcre-8.39





$ sudo yum clean all

$ sudo yum -q -y install pcre-devel.x86_64 curl-devel boost-devel readline-devel pcre-devel openssl-devel gcc

$ cd /home/www

$ wget http://story-ftp.daumkakao.io/ftp/archives/install/binaries/pcre-8.39.tar.gz

$ tar -xvf pcre-8.39.tar.gz

$ wget http://story-ftp.daumkakao.io/ftp/archives/install/binaries/openresty-1.11.2.1.tar.gz

$ tar -xvf openresty-1.11.2.1.tar.gz

$ cd /home/www/openresty-1.11.2.1 

$ ./configure -j2 --with-pcre=/home/www/pcre-8.39  --prefix=/usr/local/nginx --with-http_v2_module --with-http_stub_status_module

$ make -j2

$ sudo make install




(하위 centos버전에서는 gmake, gmake install을 추가로 실행할 수 있다)





openresty를 처음 설치한 후 빈 디렉토리를 생성한다.


$ mkdir -p /usr/local/nginx/conf




로그는 /usr/local/nginx/nginx/log에 생기니 logs 디렉토리를 /usr/local/nginx/logs로 심볼링 링크를 건다. 


$ sudo ln -sf /usr/local/nginx/nginx/logs /usr/local/nginx/logs




/usr/local/nginx/conf/nginx.conf에 설정을 추가한다. 


worker_processes 10;
error_log logs/error.log;

events {
use epoll;
multi_accept on;
worker_connections 10240;
}

http {
server {
listen 8080;
location / {
default_type text/html;
content_by_lua '
ngx.say("<p>hello, world</p>")
';
}
location /health_check.html {
access_log off;
allow all;

default_type text/html;
return 200 "OK";
}
location ~ /nginx_status {
access_log off;
allow 192.168.0.0/16;
allow 172.16.0.0/12;
allow 127.0.0.1;
deny all;
}
}
}




다음과 실행한다. 


/usr/local/nginx/bin/openresty -c /usr/local/nginx/conf/nginx.conf

/usr/local/nginx/bin/openresty -s reload -c /usr/local/nginx/conf/nginx.conf




curl http://localhost/ 를 실행해 정상적으로 동작하는지 확인한다. 




openresty를 사용하는 이유는 nginx의 이상한 문법 구조를 lua로 우아하게 표현하고 싶어서이다..




Posted by '김용환'
,


nginx의 next_upstream이 완벽하게 HA를 제공하는지 테스트하는 예시이다. 



다음 코드를 참조했다. 


https://gist.github.com/bradmontgomery/2219997#file-dummy-web-server-py



#!/usr/bin/env python

"""

Very simple HTTP server in python.

Usage::

    ./dummy-web-server.py [<port>]

Send a GET request::

    curl http://localhost

Send a HEAD request::

    curl -I http://localhost

Send a POST request::

    curl -d "foo=bar&bin=baz" http://localhost

"""

from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer

import SocketServer


class S(BaseHTTPRequestHandler):

    def _set_headers(self):

        self.send_response(200)

        self.send_header('Content-type', 'text/html')

        self.end_headers()


    def do_GET(self):

        self._set_headers()

        self.wfile.write("<html><body><h1>hi!</h1></body></html>")


    def do_HEAD(self):

        self._set_headers()

        

    def do_POST(self):

        # Doesn't do anything with posted data

        self._set_headers()

        self.wfile.write("<html><body><h1>POST!</h1></body></html>")

        

def run(server_class=HTTPServer, handler_class=S, port=80):

    server_address = ('', port)

    httpd = server_class(server_address, handler_class)

    print 'Starting httpd...'

    httpd.serve_forever()


if __name__ == "__main__":

    from sys import argv


    if len(argv) == 2:

        run(port=int(argv[1]))

    else:

        run()





nginx 설정은 다음과 같다. 


    server {

        listen       8888;

        server_name  localhost;


        #charset koi8-r;


        #access_log  logs/host.access.log  main;


        location / {

            proxy_pass http://backends;

            proxy_next_upstream error timeout invalid_header http_502 http_503 http_504;

            proxy_http_version 1.1;

            proxy_set_header Host $host;

           proxy_set_header X-Real-IP  $remote_addr;

           proxy_set_header X-Forwarded-For $remote_addr;

        }





두 디렉토리에 각각 파이썬 simple web server 를 다운받고 포트를 각각 8080, 8081로 바꿔 개별 디렉토리에서 실행한다.


$ python server.py



테스트를 해보면, 정상적으로 잘 동작한다. proxy_pass설정에 따라 처음은 8080, 다음은 8081로 잘 돌아가면서 동작한다. 


$ curl http://localhost:8888

<html><body><h1>hi!</h1></body></html>




이번에는 8081포트로 실행한 파이썬 서버에서 GET을 호출시 503 헤더 응답을 보내도록 수정하고 다시 실행한다.

curl로 get 요청을 하나 보낸다. 


127.0.0.1 - - [29/Dec/2016 20:51:05] "GET / HTTP/1.1" 503 -


내부적으로 문제가 발생한 것을 보고 8081 파이썬 서버로 GET을 보낸다. (retry 확인)


127.0.0.1 - - [29/Dec/2016 20:51:05] "GET / HTTP/1.1" 200 -




이번에는 8081포트로 실행한 파이썬 서버에 POST를 호출시 503 헤더 응답을 보내도록 수정한다.  

http 요청이 8081에 전달되면 에러가 발생한다. 


$ curl -X POST http://localhost:8888

<html><body><h1>POST!</h1></body></html>[/usr/local/nginx/conf] curl -X POST http://localhost:8888

<!DOCTYPE html>

<html>

<head>

<title>Error</title>

<style>

    body {

        width: 35em;

        margin: 0 auto;

        font-family: Tahoma, Verdana, Arial, sans-serif;

    }

</style>

</head>

<body>

<h1>An error occurred.</h1>

<p>Sorry, the page you are looking for is currently unavailable.<br/>

Please try again later.</p>

<p>If you are the system administrator of this resource then you should check

the <a href="http://nginx.org/r/error_log">error log</a> for details.</p>

<p><em>Faithfully yours, nginx.</em></p>

</body>

</html>




그리고 파이썬 서버에서는 에러가 발생했다. 

127.0.0.1 - - [29/Dec/2016 11:56:48] "POST / HTTP/1.1" 503 -


그리고 retry는 되지 않는다. 비멱등 메소드는 retry를 지원하지 않는다. 




의외로 nginx의 next stream(proxy_next_upstream)이 완벽한  L7 health check 대용으로 생각하는 분이 많다는 점이다. 

1.9.12까지는 비멱등 메소드(POST, LOCK, PATCH)에 대해서 retry를 지원했으나, 1.9.13부터는 비멱등 메소드 호출시 retry를 해주지 않는다. 



문서로 명시적으로 설명되어 있다. 


http://nginx.org/en/CHANGES

Changes with nginx 1.9.13                                        29 Mar 2016

    *) Change: non-idempotent requests (POST, LOCK, PATCH) are no longer
       passed to the next server by default if a request has been sent to a
       backend; the "non_idempotent" parameter of the "proxy_next_upstream"
       directive explicitly allows retrying such requests.

참고로 proxy_request_buffering를 사용한다 하더라도 동일하다. 

'nginx' 카테고리의 다른 글

[openresty] lua 처음 다루기  (0) 2017.01.24
openresty 1.11.2 설치  (0) 2017.01.19
[nginx+passenger] 설치  (0) 2016.06.30
[nginx] http2 적용하기  (0) 2016.06.18
[nginx] 499 에러  (0) 2016.06.10
Posted by '김용환'
,

[nginx+passenger] 설치

nginx 2016. 6. 30. 16:49



nginx-passenger 설치 내용이다. passenger를 설치하면, ruby on rails와  연동할 수 있다. 


nginx 1.6.0 + passenger 4.0.33 를 설치했다.



1) nginx 1.6.0 설치

http://nginx.org/download/





2) ruby를 설치하고, passenger 4.0.33을 설치한다.

https://rubygems.org/gems/passenger/versions/4.0.33



gem install passenger -v 4.0.33 --backtrace --verbose --local --no-rdoc --no-ri --force



nginx 설정 파일에서 passenger_root와 passenger_ruby 지시자를 추가한다. 

worker_processes 4;
error_log logs/error.log;
user www;

events {
use epoll;
multi_accept on;
worker_connections 1024;
}

http {
include mime.types;
default_type application/octet-stream;

log_format main '$request_time [$proxy_add_x_forwarded_for] - $remote_user [$time_local] '
'"$request" $status $bytes_sent '
'"$http_referer" "$http_user_agent" $remote_addr $upstream_cache_status';

access_log logs/access.log main;

underscores_in_headers on;
sendfile on;
send_timeout 10;
client_header_timeout 10;
tcp_nopush on;
keepalive_timeout 10;
server_tokens off;

gzip on;
gzip_http_version 1.1;
gzip_comp_level 3;
gzip_types text/plain application/json application/xml;
more_clear_headers 'Server' 'X-Powered-By';

passenger_root /usr/local/lib/ruby/gems/2.1.0/gems/passenger-4.0.33;
passenger_ruby /usr/local/bin/ruby;

include nginx-vhosts.conf;
}


Posted by '김용환'
,

[nginx] http2 적용하기

nginx 2016. 6. 18. 13:16



많은 사람들이 구글이 만든 http2를 점차 적용하고 있다.


https://w3techs.com/technologies/details/ce-http2/all/all





nginx는 1.9.5부터 http2를 사용할 수 있다.

https://www.nginx.com/blog/nginx-1-9-5/



구글 크롬을 지원하려면 특정 open ssl 버전이 필요하다.

https://www.nginx.com/blog/supporting-http2-google-chrome-users/



Operating SystemOpenSSL Version
CentOS/Oracle Linux/RHEL 6.5+, 7.0+1.0.1e


센트OS 에서 1.0.1e 이상의 open ssl 버전을 사용하려면, open ssl을 1.0.1e 이상을 설치하고, nginx에서 버전을 확인하고 사용한다.

아래 예시에서는 1.0.2h을 사용했다. (필요하다면 nginx 모듈을 추가할 수 있다)


--prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_secure_link_module --with-openssl=/usr/local/src/openssl-1.0.2h 




nginx 설정에는 아주 간단하게 listen 지시자 옆에 http2를 추가한다.


    listen               443 http2 default_server;



nginx 서버에 openssl가 제대로 적용되었는지, 확인한다.



$ ./nginx/sbin/nginx -V

nginx version: nginx/1.10.1

built by gcc 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)

built with OpenSSL 1.0.2h  3 May 2016

TLS SNI support enabled




클라이언트에서도 http2를 확인할 수 있다.(크롬 브라우져에서 http2가 지원되는지 확인할 수 있다.)


크롬 확장 프로그램 HTTP/2 and SPDY indicator 을 설치한다.


http2를 적용한 페이지를 열고, 크롬 브라우져의 플러그인에서 파란 번개 마크 확인할 수 있다. 

파란 번개 마크를 클릭하면, "HTTP/2 Enabled: true"가 화면에 출력되는지 확인할 수 있다.







Posted by '김용환'
,