Trial and error saat memilih HTTP Reverse Proxy

Halo semuanya!

Hari ini kami ingin berbicara tentang bagaimana tim layanan pemesanan hotel Ostrovok.ru memecahkan masalah pertumbuhan layanan-mikro, yang tugasnya adalah bertukar informasi dengan pemasok kami. Tentang pengalamannya menceritakan abadi , Pimpinan Tim DevOps di Ostrovok.ru.


Pada awalnya, microservice kecil dan melakukan fungsi-fungsi berikut:

  • menerima permintaan dari layanan lokal;
  • membuat permintaan kepada mitra;
  • menormalkan respons;
  • kembalikan hasilnya ke layanan kueri.

Namun, waktu berlalu, layanan bertambah seiring dengan jumlah mitra dan permintaan kepada mereka.

Ketika layanan tumbuh, berbagai macam masalah mulai muncul. Pemasok yang berbeda mengemukakan aturan mereka sendiri: seseorang membatasi jumlah koneksi maksimum, seseorang membatasi klien ke daftar putih.

Akibatnya, kami harus menyelesaikan masalah berikut:

  • diinginkan untuk memiliki beberapa alamat IP eksternal tetap sehingga Anda dapat memberikannya kepada mitra untuk menambahkannya ke daftar putih,
  • memiliki satu kumpulan koneksi ke semua pemasok sehingga ketika meningkatkan layanan microser kami jumlah koneksi tetap minimal,
  • mengakhiri SSL dan tetap menjaga di satu tempat, sehingga mengurangi beban pada mitra itu sendiri.

Mereka tidak berpikir untuk waktu yang lama dan langsung bertanya-tanya apa yang harus dipilih: Nginx atau Haproxy.
Pada awalnya, pendulum berayun ke arah Nginx, karena sebagian besar masalah yang terkait dengan HTTP / HTTPS saya pecahkan dengan bantuannya dan selalu puas dengan hasilnya.

Skema itu sederhana: permintaan dibuat ke Proxy Server baru kami di Nginx dengan domain berupa <partner_tag>.domain.local , di Nginx ada map tempat <partner_tag> terkait dengan alamat mitra. Alamat diambil dari map dan proxy_pass dibuat ke alamat ini.

Berikut adalah contoh map yang digunakan untuk mem-parsing domain dan memilih hulu dari daftar:

 ###     : <tag>.domain.local map $http_host $upstream_prefix { default 0; "~^([^\.]+)\." $1; } ###      map $upstream_prefix $upstream_address { include snippet.d/upstreams_map; default http://127.0.0.1:8080; } ###   upstream_host    upstream_address map $upstream_address $upstream_host { default 0; "~^https?://([^:]+)" $1; } 

Dan inilah tampilan " snippet.d/upstreams_map ":
 “one” “http://one.domain.net”; “two” “https://two.domain.org”; 

Di sini kami memiliki server{} :

 server { listen 80; location / { proxy_http_version 1.1; proxy_pass $upstream_address$request_uri; proxy_set_header Host $upstream_host; proxy_set_header X-Forwarded-For ""; proxy_set_header X-Forwarded-Port ""; proxy_set_header X-Forwarded-Proto ""; } } # service for error handling and logging server { listen 127.0.0.1:8080; location / { return 400; } location /ngx_status/ { stub_status; } } 

Semuanya keren, semuanya berfungsi. Dimungkinkan untuk mengakhiri artikel ini, jika bukan karena satu nuansa.

Saat menggunakan proxy_pass, permintaan langsung menuju ke alamat yang diinginkan, sebagai aturan, menggunakan protokol HTTP / 1.0 tanpa keepalive dan ditutup segera setelah respons selesai. Bahkan jika kita proxy_http_version 1.1 , tidak ada yang akan berubah tanpa upstream ( proxy_http_version ).

Apa yang harus dilakukan Pikiran pertama adalah memasukkan semua pemasok ke hulu, di mana server akan menjadi alamat pemasok yang kita butuhkan, dan di map simpan "tag" "upstream_name" .

Tambahkan map lain untuk menguraikan skema:

 ###     : <tag>.domain.local map $http_host $upstream_prefix { default 0; "~^([^\.]+)\." $1; } ###      map $upstream_prefix $upstream_address { include snippet.d/upstreams_map; default http://127.0.0.1:8080; } ###   upstream_host    upstream_address map $upstream_address $upstream_host { default 0; "~^https?://([^:]+)" $1; } ###   ,       https,    ,    -  http map $upstream_address $upstream_scheme { default "http://"; "~(https?://)" $1; } 

Dan buat upstreams dengan nama tag:
  upstream one { keepalive 64; server one.domain.com; } upstream two { keepalive 64; server two.domain.net; } 

Server itu sendiri sedikit dimodifikasi untuk memperhitungkan skema dan menggunakan nama upstream alih-alih alamat:

 server { listen 80; location / { proxy_http_version 1.1; proxy_pass $upstream_scheme$upstream_prefix$request_uri; proxy_set_header Host $upstream_host; proxy_set_header X-Forwarded-For ""; proxy_set_header X-Forwarded-Port ""; proxy_set_header X-Forwarded-Proto ""; } } # service for error handling and logging server { listen 127.0.0.1:8080; location / { return 400; } location /ngx_status/ { stub_status; } } 

Bagus Solusinya bekerja, tambahkan direktif keepalive di setiap hulu, setel proxy_http_version 1.1 , - sekarang kami memiliki kumpulan koneksi, dan semuanya berfungsi sebagaimana mestinya.

Kali ini, Anda pasti bisa menyelesaikan artikel dan minum teh. Atau tidak?

Memang, saat kita minum teh, salah satu pemasok dapat mengubah alamat IP atau kelompok alamat di bawah domain yang sama (hai, Amazon), sehingga salah satu pemasok dapat jatuh di puncak pesta teh kami.

Nah, apa yang harus dilakukan? Nginx memiliki nuansa yang menarik: saat memuat ulang, Nginx dapat memulihkan server di dalam upstream ke alamat baru dan menempatkan lalu lintas di atasnya. Secara umum, juga solusi. Lemparkan dalam cron reload nginx setiap 5 menit dan terus minum teh.

Tapi menurut saya itu keputusan yang biasa saja, jadi saya mulai memandang curiga terhadap Haproxy.

Haproxy memiliki kemampuan untuk menentukan dns resolvers dan mengkonfigurasi dns cache . Dengan demikian, Haproxy akan memperbarui dns cache jika entri di dalamnya telah kedaluwarsa, dan mengganti alamat untuk upstream jika sudah berubah.

Hebat! Sekarang terserah pengaturan.

Berikut adalah contoh konfigurasi singkat untuk Haproxy:

 frontend http bind *:80 http-request del-header X-Forwarded-For http-request del-header X-Forwarded-Port http-request del-header X-Forwarded-Proto capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 acl host_present hdr(host) -m len gt 0 use_backend %[req.hdr(host),lower,field(1,'.')] if host_present default_backend default resolvers dns hold valid 1s timeout retry 100ms nameserver dns1 1.1.1.1:53 backend one http-request set-header Host one.domain.com server one--one.domain.com one.domain.com:80 resolvers dns check backend two http-request set-header Host two.domain.net server two--two.domain.net two.domain.net:443 resolvers dns check ssl verify none check-sni two.domain.net sni str(two.domain.net) 

Segalanya tampak berfungsi sebagaimana mestinya saat ini. Satu-satunya hal yang saya tidak suka tentang Haproxy adalah kompleksitas deskripsi konfigurasi. Anda perlu membuat banyak teks untuk menambahkan satu yang berfungsi di hulu. Tetapi kemalasan adalah mesin kemajuan: jika Anda tidak ingin menulis hal yang sama, tulis generator.

Saya sudah memiliki peta dari Nginx dengan format "tag" "upstream" , jadi saya memutuskan untuk mengambilnya sebagai dasar, parsing dan menghasilkan backend haproxy berdasarkan nilai-nilai ini.

 #! /usr/bin/env bash haproxy_backend_map_file=./root/etc/haproxy/snippet.d/name_domain_map haproxy_backends_file=./root/etc/haproxy/99_backends.cfg nginx_map_file=./nginx_map while getopts 'n:b:m:' OPT;do case ${OPT} in n) nginx_map_file=${OPTARG} ;; b) haproxy_backends_file=${OPTARG} ;; m) haproxy_backend_map_file=${OPTARG} ;; *) echo 'Usage: ${0} -n [nginx_map_file] -b [haproxy_backends_file] -m [haproxy_backend_map_file]' exit esac done function write_backend(){ local tag=$1 local domain=$2 local port=$3 local server_options="resolvers dns check" [ -n "${4}" ] && local ssl_options="ssl verify none check-sni ${domain} sni str(${domain})" [ -n "${4}" ] && server_options+=" ${ssl_options}" cat >> ${haproxy_backends_file} <<EOF backend ${tag} http-request set-header Host ${domain} server ${tag}--${domain} ${domain}:${port} ${server_options} EOF } :> ${haproxy_backends_file} :> ${haproxy_backend_map_file} while read tag addr;do tag=${tag//\"/} [ -z "${tag:0}" ] && continue [ "${tag:0:1}" == "#" ] && continue IFS=":" read scheme domain port <<<${addr//;} unset IFS domain=${domain//\/} case ${scheme} in http) port=${port:-80} write_backend ${tag} ${domain} ${port} ;; https) port=${port:-443} write_backend ${tag} ${domain} ${port} 1 esac done < <(sort -V ${nginx_map_file}) 

Sekarang yang kita butuhkan adalah menambahkan host baru di nginx_map, mulai generator dan dapatkan konfigurasi haproxy siap.

Mungkin itu saja untuk hari ini. Artikel ini lebih mengacu pada pengantar dan dikhususkan untuk masalah memilih solusi dan integrasinya ke lingkungan saat ini.

Pada artikel selanjutnya saya akan memberi tahu Anda lebih banyak tentang jebakan apa yang kami temui saat menggunakan Haproxy, yang metriknya ternyata berguna untuk dipantau, dan apa sebenarnya yang harus dioptimalkan dalam sistem untuk mendapatkan kinerja terbaik dari server.

Terima kasih atas perhatian Anda, sampai jumpa!

Source: https://habr.com/ru/post/id436992/


All Articles