NGINX: reverse proxy

Başlığı tam olarak nasıl atmam gerektiğinden emin olamadım. Geniş bir konu. En mantıklısının bu şekilde olacağını hisettim. O halde başlığı Reverse Proxy olarak çerçevelediğimden dolayı bununla ilişkili notlar alacağım. Aslında uzun bir yazı olacak ve henüz bir planım yok; artık ne çıkarsa.

Bir şeyleri öğrenme patikamda şuanlık NGINX bölgesindeyim -her yazıda tekrarlamak sıkıcı olsa da teorik hatalarımı düzeltebilirsiniz-. Bu notta belli başlı temel şeyleri ele alarak ilerlemek istiyorum. Prensip olarak notlarımı aldığım bu blogta text dışında herhangi bir content type kullanmıyorum. Bundan dolayı bu içerikte de görseller olmadan ilerleyeceğim. Minimal tutmak eğlenceli oluyor. Umarım code snippetlar ile mantığını betimletebilirim. O halde başlayalım. 

Basit bir architecture düşünürsek şu şekilde bir yapıya ulaşmamız gerekli: client -> proxy server -> upstream server. Upstream server'da iki farklı server kurabiliriz; en azından proxy header directive konusu için faydalı olur. Client tarafı zaten web browser ile tamam. Proxy Server için bir NGINX ayağa kaldıralım. Tabii yine Vagrant kullanacağım. Kesinlikle artılar eksiler olduğuna eminim ama ben canım öyle istediğinden dolayı proxy server tarafını CentOS-7 ile upstream server tarafını da Ubuntu ile ayağa kaldırmak istiyorum. En başta uzun bir yazı olacağını söylediğimden dolayı uzatmak için de elimden gerekeni yaparım. O yüzden Vagrant ayarlarımız ile başlayalım.

Vagrant.configure("2") do |config|

### nginx vm ###
  config.vm.define "web01" do |web01|
    web01.vm.box = "centos/7"
    web01.vm.hostname = "web01"
    web01.vm.network "private_network", ip: "192.168.61.12"
    web01.vm.network "public_network"
  end

### upstream server vm ###
  config.vm.define "app01" do |app01|
    app01.vm.box = "ubuntu/bionic64"
    app01.vm.hostname = "app01"
    app01.vm.network "private_network", ip: "192.168.61.13"
    app01.vm.network "public_network"
  end
end

Vagrantfile'da provision ayarlarını yapmak isterdim ama üzerlerinde durarak gideceğim için önden VMleri ayağı kaldırırken herhangi komut çalıştırmak istemedim. Static IP ve boxları belirlediğimize göre normal bir şekilde VMleri ayağa kaldıralım.

$ vagrant up
Bringing machine 'web01' up with 'virtualbox' provider...
Bringing machine 'app01' up with 'virtualbox' provider...
==> web01: Importing base box 'centos/7'...
...
...
...
==> app01: Importing base box 'ubuntu/bionic64'...
...
...
...

 NGINX için web01 ile SSH bağlantısı kuralım.

vagrant ssh web01
[vagrant@web01 ~]$ 

Buradan sonra root olarak devam edeceğim.

$ sudo -i
[root@web01 ~]$

Paketlerimizi kontrol edelim.

$ rpm -qa | grep nginx
$ rpm -qa | grep epel-release

Her iki komut ile herhangi bir çıktı almadık. NGINX için epel-release paketine ihtiyacımız olduğundan dolayı gerekli paketi yükleyelim.

$ yum update
$ yum upgrade -y
$ yum install epel-release -y
$ rpm -qa | grep epel-release
epel-release-7-11.noarch

Gerekli paketimiz kurulduğuna göre NGINX'e geçebiliriz fakat ondan önce SELinux'u disabled konumuna getirmek istiyorum. Güvenlik tarafı ile uğraşmamak için.

$ sestatus
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      31

$ vi /etc/selinux/config
      1 
      2 # This file controls the state of SELinux on the system.
      3 # SELINUX= can take one of these three values:
      4 #     enforcing - SELinux security policy is enforced.
      5 #     permissive - SELinux prints warnings instead of enforcing.
      6 #     disabled - No SELinux policy is loaded.
      7 SELINUX=disabled
      8 # SELINUXTYPE= can take one of three values:
      9 #     targeted - Targeted processes are protected,
     10 #     minimum - Modification of targeted policy. Only selected processes are protected.
     11 #     mls - Multi Level Security protection.
     12 SELINUXTYPE=targeted

$ shutdown -r now
Connection to 127.0.0.1 closed by remote host.
Connection to 127.0.0.1 closed.

$ vagrant ssh web01 
Last login: Thu Aug 25 02:10:47 2022 from 10.0.2.2

$ sudo -i

$ sestatus
SELinux status:                 disabled

SELinux'u ayarladıktan sonra NGINX'i kuralım, servisi başlatalım ve kontrol edelim.

$ yum install nginx -y

$ service nginx start
Redirecting to /bin/systemctl start nginx.service

$ netstat -tunlp | grep nginx
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      20286/nginx: master 
tcp6       0      0 :::80                   :::*                    LISTEN      20286/nginx: master 

80 portunda çalıştığını doğruladıktan sonra bir de client ile request atarak logları kontrol edelim.

$ echo > /var/log/nginx/access.log 
$ echo > /var/log/nginx/error.log 

$ tail -f /var/log/nginx/*
==> /var/log/nginx/access.log <==


==> /var/log/nginx/error.log <==

##Request sonrası access.log##

==> /var/log/nginx/access.log <==
192.168.1.101 - - [25/Aug/2022:03:32:59 +0000] "GET / HTTP/1.1" 200 4833 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:103.0) Gecko/20100101 Firefox/103.0" "-"
192.168.1.101 - - [25/Aug/2022:03:33:59 +0000] "GET / HTTP/1.1" 200 4833 "-" "curl/7.68.0" "-"

İlk requesti Firefox ile ikincisini curl ile attım. Herhangi bir hata ile karşlaşmadığımıza göre NGINX ayarlarını yapabiliriz. web01 proxy server olacağından dolayı proxy_pass directive'e (ingilizce kelimelere Türkçe ekler biraz zor oluyor) ihtiyacımız var. NGINX ayarlarının bulunduğu nginx.conf'u incelediğimizde include directive bize .conf uzantısı altında istediğimiz yapılandırmaları ayarlayabileceğimizi gösteriyor.

$ cat /etc/nginx/nginx.conf
...
...

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;
...
...

include /etc/nginx/conf.d/*.conf; directive ile ne yapmamız gerektiği açıkca belirtiliyor. Tüm default yapılandırmaları dahil etmedim zaten bu dosya ile bir iki kez daha işimiz olacak. Ondan dolayı gerekli alanları gösteriyorum. O halde ihtiyacamız olan konumda <name>.conf isimli bir dosya oluşturalım ve çok basit düzeyde düzenleyelim.

$ vi /etc/nginx/conf.d/web01.conf
server {
    server_name _;

    location / {
      proxy_pass http://192.168.61.13;
    }
}

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

server_name directive için buraya link bırakıyorum. Benden duymak yerine buradaki daha temiz bir anlatımı tercih etmelisiniz. proxy_pass dırective ile backend server'a requestleri yönlendirdik yani request routing işlemini gerçekleştirdik. Hemen kafamızda canlandıralım, hostumuz example.com olsun. Client tarafından example.com/'a gelen bir request buraya yönlendirilecek. Son satırdaki komut ile NGINX yapılandırma dosyalarımızda herhangi bir syntax veya başka bir hatanın olup olmadığını kontrol ettik.

Burada işimiz şimdilik bitti ama yakında geri döneceğiz. İşlerin karışmaması için web01 sunucusu dediğimde burayı anımsayalım. O halde şimdi app01'e bağlanarak orada ki işlemlerimiz gerçekleştirelim. web01 ssh bağlantısını koparmıyorum ve yeni bir terminal ile devam ediyorum. Yukarıdaki işlemleri tekrarlayacağımdan dolayı NGINX kurulumu da dahil hepsini tek code snippet ile göstereceğim. Box Ubuntu olduğundan dolayı epel-release paketine ihtiyacımız yok ki zaten EPEL bir ubuntu paketi değil.

$ vagrant ssh app01
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-189-generic x86_64)

$ sudo -i 

$ apt update 

$ apt upgrade -y 

$ apt install nginx -y 

$ service nginx start 

$ service nginx status 
 ● nginx.service - A high performance web server and a reverse proxy server
   Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
   Active: active (running) since Thu 2022-08-25 04:25:24 UTC; 20s ago
   ...
   ...

$ echo > /var/log/nginx/access.log 

$ echo > /var/log/nginx/error.log 

$ tail -f /var/log/nginx/*.log
==> /var/log/nginx/access.log <==


==> /var/log/nginx/error.log <==

## Firefox ile GET Request sonrası ## 

==> /var/log/nginx/access.log <==
192.168.61.1 - - [25/Aug/2022:04:31:47 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"

Buraya kadar herhangi bir sıkıntı yok. Burasıni Upstream server olarak düşündüğümüze göre bu istekle gerekli ayarlamaları yapalım. web01'de olduğu gibi NGINX'in *.conf dosyalarını aradığı dizine kendi ayarlarımızı yapılandıralım.

$ vim /etc/nginx/conf.d/app01.conf
server {
    server_name 192.168.61.13;

    location / {
      root /var/www/app01;
      index index.html;
    } 
}

$ nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
   

server_name belirledikten sonra root path'ini belirledik. Bu pathi oluşturalım ve izinlerini verelim. İzinleri vermeden önce NGINX genel yapılandırmalarından bir şey göstermek istiyorum.

$ cat /etc/nginx/nginx.conf 
user www-data;
worker_processes auto;
pid /run/nginx.pid;

$ ps -aux | grep nginx
root     21499  0.0  0.1 141128  1540 ?        Ss   04:25   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data 21501  0.0  0.6 143800  6400 ?        S    04:25   0:00 nginx: worker process
www-data 21502  0.0  0.6 143800  6400 ?        S    04:25   0:00 nginx: worker process

İlk komut satırı ile www-data'nın user olarak belirlendiğini görüyoruz. ps komutu ile çalışan processesleri listelediğimizde worker process'lerin www-data kullanıcısı altında, master process'in ise root altında çalıştığını görüyoruz.. Master process aslında yapılandırmaları değerlendiren ve okuyan bir processtir. NGINX servisi hangi kullanıcı altında başlatıldıysa o kullanıcın altında çalışır fakat worker processler daha farklıdır. Zaten şu komutu çalıştırdığımızda worker processlerin aslında master process altında çalıştığını görebiliriz.

$ ps -aux | grep nginx
root     21499  0.0  0.1 141128  1540 ?        Ss   04:25   0:00 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data 21501  0.0  0.6 143800  6400 ?        S    04:25   0:00  \_ nginx: worker process
www-data 21502  0.0  0.6 143800  6400 ?        S    04:25   0:00  \_ nginx: worker process

Burayı anlatmamdaki sebep şimdi root için oluşturduğumuz path'i www-data ile ilişkilendirmemiz gerekecek. Belki mantığını açıklayabilirim diye düşündüm. Devam edelim.

$ mkdir /var/www/app01

$ rm -rf /var/www/html/

$ vim /var/www/app01/index.html
#Burasi app01 sunucusu. 
 
$ ls -al /var/www/
drwxr-xr-x  2 root root 4096 Aug 25 04:53 app01

$ chown -R www-data /var/www/

$ ls -al /var/www/
drwxr-xr-x  2 www-data www-data 4096 Aug 25 04:54 app01

$ service nginx reload

$ curl http://192.168.61.13/
Burasi app01 sunucusu.

Path'i oluşturduk. Gereksiz html klasörünü kaldırdık. index.html oluşturarak içine basit bir content girdik. root ile ilişkili olan path'i www-data ile değiştirdik. servisi reload ederek request attık ve hiçbir sıkıntı gözükmüyor. Buraya kadar çok temiz geldik. Şuanda basit düzeyde proxy ve upstream serverlarımız kurulu durumda. Dilerseniz artık web01 sunucusuna request atalım ve neler olduğunu inceleyelim.

$ curl http://192.168.61.12/
Burasi app01 sunucusu.

Boom! Evet, istediğimiz şeyi elde ettik. Gelen requesti app01 sunucusuna yönlendirdik. Bir de Logging yaparak inceleyelim. Her iki tarafta da access.log dosyalarını izlerken Client yani Firefox ile web01 yani proxy sunucusuna GET request atacağız. Requestleri atmadan önce IP adreslerini güzelce gruplayalım. İncelerken kafamız karışmasın.

burak@comp:~$ ifconfig
        inet 192.168.61.1

root@app01:/$ ifconfig
        inet 192.168.61.13  
       
root@web01:/$ ifconfig
        inet 192.168.61.12

Sırasıyla benim, app01 ve web01'in IP adresi. Şimdi kendi tarayıcımdan proxy server'a GET request işlemi gerçekleştireceğim.

root@app01:/$ echo > /var/log/nginx/access.log 
root@web01:/$ echo > /var/log/nginx/access.log

root@web01:/$ tail -f /var/log/nginx/access.log
192.168.61.1 - - [25/Aug/2022:05:16:33 +0000] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0" "-"

root@app01:/$ tail -f /var/log/nginx/access.log 
192.168.61.12 - - [25/Aug/2022:05:16:33 +0000] "GET / HTTP/1.0" 304 0 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:104.0) Gecko/20100101 Firefox/104.0"

Logları incelediğimiz zaman proxy server'a request attığımdan kaynağın benim IP adresim olduğunu görüyorum fakat proxy requesti yönlendirme işlemini gerçekleştirdikten sonra backend server ise gelen requestin kaynağın proxy server olarak algılıyor. Aslında tüm işlemlerin tamamlandığını söyleyebiliriz. Buraya kadar gelmişken bir konuyu daha ele alıp bitirelim. Uzadıkça uzayacak yoksa. Normalde bu durum çok istenilen bir süreç değil. Her ne kadar proxy server aracılığıyla requestler backende yönlendirilse de backend tarafı, gelen requestin benim tarafımdan gönderildiğini anlaması lazım. Bunun için ise proxy_set_header directive işin içine giriyor. Kısaca buna da değinip konuyu kapatalım. İlk yapmamız gereken web01 yani proxy server tarafında directive ayarlayalım.

$ vi /etc/nginx/conf.d/web01.conf
server {
    server_name _;

    location / {
      proxy_pass http://192.168.61.13;
      proxy_set_header X-Real-IP $remote_addr; #YENI
    }
}

proxy_set_header directive'ne X-Real-IP header'ını ekledik ve değer olarak $remote_addr değişkenini belirledik. Bu şekilde backende yönlendirilen requestlerde ayarlamış olduğumuz header Client'ın adresini tutacak. Bunun içi bir ayar daha yapmamız lazım. app01'ın NGINX genel yapılandırma dosyasını açalım ve düzenleyelim.

$ vi /etc/nginx/nginx.conf
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" "http_x_real_ip"';

$ service nginx reload
Redirecting to /bin/systemctl reload nginx.service

Artık Client tarafından gelen requestlerde Client'ın adresini görebiliriz. Bu yazı şimdilik burada son bulsun.


İletişime geçmek, yorum bırakmak veya hatalarımı düzetlmek istersen mail atabilirsin.

iletişim için tıklama yeri