본문 바로가기
서버 및 보안

Varnish (software)

by 다움위키 2023. 12. 25.

스퀴드와 바니쉬 중에 어떤 것을 선택할지 고민이 되기도 합니다. 여기의 투표를 보면, 압도적으로 바니쉬를 많이 사용하는 것으로 집계가 되었습니다. 어쨌든, 오픈-소스 바니쉬는 SSL/TLS를 지원하지 않고, 상대적으로 많은 CPU와 메모리 자원을 차지합니다. 이것을 해결하는 방법은 Varnish (software)/SSL Redirect with Nginx 또는 HAProxy 등이 있습니다. 단지 전자의 경우는 문제가 있습니다.

바니쉬(Varnish)는 컨텐츠가 많은 동적 웹 사이트와 마찬가지로 API에 대해 설계된 HTTP 가속기입니다. 클라이언트-측 캐시로 수명을 시작했든 스퀴드, 또는 주로 원본 서버인, 아파치nginx엔진엑스]]와 같은 다른 웹 가속기와 대조적으로, 바니쉬는 HTTP 가속기로 설계되었습니다. 바니쉬는, 종종 FTP, SMTP 및 다른 네트워크 프로토콜을 지원하는 다른 프록시 서버와 달리 HTTP에 독점적으로 집중합니다.

바니쉬는 위키피디아를 포함하는 웹-사이트, The New York Times, The Guardian, The Hindu, Corriere della Sera와 같은 온라인 신문 사이트, Facebook, Twitter, Reddit, Vimeo, 및 Tumblr과 같은 소셜 미디어와 컨텐츠 사이트에서 사용됩니다. 2012년 웹 사이트의 상위 10,000개 사이트 중 5%가 이 소프트웨어를 사용했습니다.

Introduction

바니쉬는 웹 캐시 및 HTTP 가속기로써, 캐시된 컨텐츠를 제공하거나, 또는 서버에서 컨텐츠를 검색하여 캐시할 수 있습니다. 이렇게 하면, 많은 클라이언트에게 서비스를 제공하거나 많은 요청을 하는 웹 서버의 I/O 부담을 줄일 수 있습니다.

Installation

데비안 저장소에서 설치할 수 있습니다:

  • sudo apt install varnish

다음 명령으로 설치 상태를 확인할 수 있습니다:

  • ss -tlnf inet
State   Recv-Q   Send-Q      Local Address:Port       Peer Address:Port   
LISTEN  0        511               0.0.0.0:80              0.0.0.0:*      
LISTEN  0        1000              0.0.0.0:6081            0.0.0.0:*      
LISTEN  0        10              127.0.0.1:6082            0.0.0.0:*

결과로부터, 엔진엑스 서버가 포트 80에서 청취하고 있고, 바니쉬는 6081 및 6082 포트를 모두 사용하고 있음을 알 수 있습니다.

Configure Nginx Server

엔진엑스 서버의 역할은 바니쉬 캐시 서버 뒤에 놓을 것이기 때문에, 기본 포트 80을 다른 포트로 할당하도록 /etc/nginx/site-available/default를 수정합니다.

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

엔진엑스를 재시작합니다:

  • sudo systemctl restart nginx

제대로 포트가 바뀌었는지 확인을 합니다.

  • ss -tlnf inet
State   Recv-Q   Send-Q      Local Address:Port       Peer Address:Port   
LISTEN  0        511               0.0.0.0:8080            0.0.0.0:*      
LISTEN  0        1000              0.0.0.0:6081            0.0.0.0:*      
LISTEN  0        10              127.0.0.1:6082            0.0.0.0:*

Setup Varnish Cache Server

엔진엑스로 접근하면, 바니쉬 캐시 서버를 통해 트래픽을 라우팅하기 위해서, 바니쉬 캐시 서버의 포트를 80번으로 수신 대기하도록 변경해야 합니다. 이렇게 하려면 systemd 구성 파일 /lib/systemd/system/varnish.service를 수정해야 합니다. 바니쉬가 사용하던 6081 포트를 80 포트로 바꾸어 줍니다:

[Unit]
Description=Varnish HTTP accelerator
Documentation=https://www.varnish-cache.org/docs/5.2/ man:varnishd

[Service]
Type=simple
LimitNOFILE=131072
LimitMEMLOCK=82000
ExecStart=/usr/sbin/varnishd -j unix,user=vcache -F -a localhost:80 -T localhost:6082 -f /etc/varnish/default.vcl -S /etc/varnish/secret -s malloc,256m
ExecReload=/usr/share/varnish/varnishreload
ProtectSystem=full
ProtectHome=true
PrivateTmp=true
PrivateDevices=true

[Install]
WantedBy=multi-user.target

이제 엔진엑스에서 사용하는 8080 포트를 바니쉬 설정에서 변경해 주어야 합니다. 이를 수정할 때에, 엔진엑스와 바니쉬가 같은 호스트일 경우에는 127.0.0.1을 사용하고, 그렇지 않으면, 엔진엑스 서버의 ip 주소를 /etc/varnish/default.vcl를 수정해 주어야 합니다:

# Default backend definition. Set this to point to your content server.
backend default {
    .host = "127.0.0.1";
    .port = "8080";

이제, systemd 데몬을 reload하고 바니쉬 캐시 서버를 재시작합니다:

  • sudo systemctl daemon-reload
  • sudo service varnish restart

모든 설정이 끝났습니다. 마지막으로 설정이 제대로 동작하는지 확인을 합니다:

  • ss -tlnf inet
State   Recv-Q   Send-Q      Local Address:Port       Peer Address:Port   
LISTEN  0        1000              0.0.0.0:80              0.0.0.0:*      
LISTEN  0        511               0.0.0.0:8080            0.0.0.0:*   
LISTEN  0        10              127.0.0.1:6082            0.0.0.0:*

Testing Varnish Cache Server

바니쉬 캐시 서버 구성을 테스트하는 가장 간단한 방법은 curl 명령을 사용하는 것입니다. 자신의 호스트명을 통해 바니쉬 캐시 서버 IP 주소를 확인할 수 있습니다:

  • curl -I dawoum.duckdns.org
HTTP/1.1 301 Moved Permanently
Server: nginx/1.14.0 (Ubuntu)
Date: Mon, 07 May 2018 20:45:04 GMT
Content-Type: text/html; charset=utf-8
X-Content-Type-Options: nosniff
Vary: Accept-Encoding, Cookie
Cache-Control: s-maxage=1200, must-revalidate, max-age=0
Last-Modified: Mon, 07 May 2018 20:45:04 GMT
Location: http://dawoum.duckdns.org/wiki/Main_Page
X-Varnish: 65560 3
Age: 164
Via: 1.1 varnish (Varnish/5.2)
Connection: keep-alive

결과에서 Via: 1.1 varnish (Varnish/5.2)를 볼 수 있습니다.

다음으로, 호스트명으로 웹브라우저에서 URL http://dawoum.duckdns.org로 접근해 봅니다. 정상적으로 출력되지 않을 경우에는 설정을 확인해야 합니다.

잘 작동하고 있다면, varnishstat 명령을 사용하여 바니쉬 캐싱 통계를 확인할 수 있습니다:

  • sudo varnishstat

HAProxy 설정 후에 통계입니다.

MGT.uptime                 1+02:33:08
MAIN.uptime                1+02:33:09
MAIN.sess_conn                  61215          0.00          0.64
MAIN.client_req_400                 1          0.00          0.00
MAIN.client_req                112791          0.00          1.18
MAIN.cache_hit                  24574          0.00          0.26
MAIN.cache_hit_grace             4941          0.00          0.05
MAIN.cache_hitmiss                311          0.00          0.00
MAIN.cache_miss                 37183          0.00          0.39
MAIN.backend_conn                7344          0.00          0.08
MAIN.backend_reuse              84070          0.00          0.88
MAIN.backend_recycle            91384          0.00          0.96
MAIN.fetch_length               76630          0.00          0.80
MAIN.fetch_chunked              14484          0.00          0.15

Varnish setting from MediaWiki

미디어위키에서 추천하는 설정을 추가해 줍니다. 5.x에 대한 부분이 없기 때문에, 가장 가까운 버전 설정을 /etc/varnish/default.vcl를 추가합니다.

# access control list for "purge": open to only localhost and other local nodes
acl purge {
    "127.0.0.1";
}

# vcl_recv is called whenever a request is received 
sub vcl_recv {
        # Serve objects up to 2 minutes past their expiry if the backend
        # is slow to respond.
        # set req.grace = 120s;
        set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
        set req.backend_hint= default;
 
        # This uses the ACL action called "purge". Basically if a request to
        # PURGE the cache comes from anywhere other than localhost, ignore it.
        if (req.method == "PURGE") {
            if (!client.ip ~ purge) {
                return (synth(405, "Not allowed."));
            } else {
                return (purge);
            }
        }
 
        # Pass any requests that Varnish does not understand straight to the backend.
        if (req.method != "GET" && req.method != "HEAD" &&
            req.method != "PUT" && req.method != "POST" &&
            req.method != "TRACE" && req.method != "OPTIONS" &&
            req.method != "DELETE") {
                return (pipe);
        } /* Non-RFC2616 or CONNECT which is weird. */
 
        # Pass anything other than GET and HEAD directly.
        if (req.method != "GET" && req.method != "HEAD") {
            return (pass);
        }      /* We only deal with GET and HEAD by default */
 
        # Pass requests from logged-in users directly.
        # Only detect cookies with "session" and "Token" in file name, otherwise nothing get cached.
        if (req.http.Authorization || req.http.Cookie ~ "session" || req.http.Cookie ~ "Token") {
            return (pass);
        } /* Not cacheable by default */
 
        # Pass any requests with the "If-None-Match" header directly.
        if (req.http.If-None-Match) {
            return (pass);
        }
 
        # Force lookup if the request is a no-cache request from the client.
        if (req.http.Cache-Control ~ "no-cache") {
            ban(req.url);
        }
 
        # normalize Accept-Encoding to reduce vary
        if (req.http.Accept-Encoding) {
          if (req.http.User-Agent ~ "MSIE 6") {
            unset req.http.Accept-Encoding;
          } elsif (req.http.Accept-Encoding ~ "gzip") {
            set req.http.Accept-Encoding = "gzip";
          } elsif (req.http.Accept-Encoding ~ "deflate") {
            set req.http.Accept-Encoding = "deflate";
          } else {
            unset req.http.Accept-Encoding;
          }
        }
 
        return (hash);
}

sub vcl_pipe {
        # Note that only the first request to the backend will have
        # X-Forwarded-For set.  If you use X-Forwarded-For and want to
        # have it set for all requests, make sure to have:
        # set req.http.connection = "close";
 
        # This is otherwise not necessary if you do not do any request rewriting.
 
        set req.http.connection = "close";
}

# Called if the cache has a copy of the page.
sub vcl_hit {
        if (req.method == "PURGE") {
            ban(req.url);
            return (synth(200, "Purged"));
        }
 
        if (!obj.ttl > 0s) {
            return (pass);
        }
}
 
# Called if the cache does not have a copy of the page.
sub vcl_miss {
        if (req.method == "PURGE")  {
            return (synth(200, "Not in cache"));
        }
}

# Called after a document has been successfully retrieved from the backend.
sub vcl_backend_response {
        # set minimum timeouts to auto-discard stored objects
        set beresp.grace = 120s;
 
        if (beresp.ttl < 48h) {
          set beresp.ttl = 48h;
        }       
 
        if (!beresp.ttl > 0s) {
          set beresp.uncacheable = true;
          return (deliver);
        }
 
        if (beresp.http.Set-Cookie) {
          set beresp.uncacheable = true;
          return (deliver);
        }
 
#       if (beresp.http.Cache-Control ~ "(private|no-cache|no-store)") {
#          set beresp.uncacheable = true;
#          return (deliver);
#        }
 
        if (beresp.http.Authorization && !beresp.http.Cache-Control ~ "public") {
          set beresp.uncacheable = true;
          return (deliver);
        }

        return (deliver);
}

External Resources