Dokumentasi Haproxy mengembara sejarah, atau apa yang harus dicari saat mengonfigurasinya

Halo lagi!

Terakhir kali, kami berbicara tentang memilih alat di Ostrovok.ru untuk memecahkan masalah proksi sejumlah besar permintaan ke layanan eksternal, tanpa menempatkan siapa pun pada saat yang bersamaan. Artikel itu berakhir dengan pilihan Haproxy . Hari ini saya akan membagikan nuansa yang harus saya hadapi ketika menggunakan solusi ini.



Konfigurasi Haproxy


Kesulitan pertama adalah bahwa opsi maxconn berbeda tergantung pada konteksnya:


Karena kebiasaan, saya hanya menyetel opsi pertama ( performance tuning ). Inilah yang dikatakan dokumentasi tentang opsi ini:
Tetapkan jumlah maksimum per-proses koneksi konkuren ke <nomor>. Ini
setara dengan argumen baris perintah "-n". Proxy akan berhenti menerima
koneksi saat batas ini tercapai.

Tampaknya apa yang dibutuhkan. Namun, ketika saya menemukan fakta bahwa koneksi baru ke proxy tidak langsung pergi, saya mulai membaca dokumentasi lebih hati-hati, dan di sana saya sudah menemukan parameter kedua ( bind options ):
Batasi soket untuk jumlah koneksi bersamaan ini. Ekstra
koneksi akan tetap berada dalam jaminan sistem sampai koneksi tercapai
dilepaskan. Jika tidak ditentukan, batasnya akan sama dengan maxconn frontend.

Jadi, frontends maxconn pergi, lalu cari frontends maxconn :
Perbaiki jumlah maksimum koneksi konkuren di frontend
...
Secara default, nilai ini diatur ke 2000.

Bagus, apa yang kamu butuhkan. Tambahkan ke konfigurasi:

 global daemon maxconn 524288 ... defaults mode http maxconn 524288 

Gag berikutnya adalah bahwa Haproxy adalah single-threaded. Saya sangat terbiasa dengan model di Nginx, jadi nuansa ini selalu membuat saya depresi. Tapi jangan putus asa - Willy Tarreau ( pengembang Haproxy ) mengerti apa yang dia lakukan, jadi dia menambahkan opsi - nbproc .

Namun, langsung dalam dokumentasi tertulis:
MENGGUNAKAN PROSES GANDA
LEBIH KERAS UNTUK MENGUNDANG DAN BENAR-BENAR TIDAK DIKUTIP.
Opsi ini benar-benar dapat menyebabkan sakit kepala jika Anda membutuhkan:

  • batasi jumlah permintaan / koneksi ke server (karena Anda tidak akan memiliki satu proses dengan satu penghitung, tetapi banyak proses, dan masing-masing memiliki penghitungnya sendiri);
  • Kumpulkan statistik dari soket manajemen Haproxy
  • mengaktifkan / menonaktifkan backend melalui soket kontrol;
  • ... mungkin sesuatu yang lain. Β― \ _ (ツ) _ / Β―

Namun demikian, para dewa memberi kami prosesor multi-core, jadi saya ingin menggunakannya secara maksimal. Dalam kasus saya, ada empat core dalam dua core fisik. Untuk Haproxy, saya memilih inti pertama, dan itu terlihat seperti ini:

  nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 

Menggunakan cpu-map, kami mengikat proses Haproxy ke inti tertentu. Penjadwal OS tidak perlu lagi memikirkan di mana harus merencanakan Haproxy, sehingga menjaga content switch tetap dingin, dan cache cpu tetap hangat.

Ada banyak buffer, tetapi tidak dalam kasus kami


  • tune.bufsize - dalam kasus kami itu tidak perlu untuk menjalankannya, tetapi jika Anda memiliki kesalahan dengan kode 400 (Bad Request) , maka ini mungkin kasus Anda.
  • tune.http.cookielen - jika Anda mendistribusikan cookie besar kepada pengguna, maka, untuk menghindari kerusakan selama transmisi melalui jaringan, mungkin masuk akal untuk meningkatkan buffer ini juga.
  • tune.http.maxhdr adalah sumber lain yang mungkin dari 400 kode respons jika Anda memiliki terlalu banyak header.

Sekarang pertimbangkan hal-hal tingkat yang lebih rendah


tune.rcvbuf.client / tune.rcvbuf.server , tune.sndbuf.client / tune.sndbuf.server - dokumentasi mengatakan yang berikut:
Seharusnya tidak pernah diatur, dan ukuran default (0) memungkinkan kernel autotune nilai ini tergantung pada jumlah memori yang tersedia.

Tetapi bagi saya, yang jelas lebih baik daripada yang implisit, jadi saya memaksakan nilai-nilai opsi ini untuk memastikan hari esok.

Dan parameter lain yang tidak terkait dengan buffer, tetapi cukup penting adalah tune.maxaccept .
Mengatur jumlah maksimum koneksi berturut-turut yang mungkin diterima oleh suatu proses dalam a
baris sebelum beralih ke pekerjaan lain. Dalam mode proses tunggal, angka yang lebih tinggi
memberikan kinerja yang lebih baik pada tingkat koneksi yang tinggi. Namun dalam multi-proses
mode, menjaga sedikit keadilan antara proses umumnya lebih baik
meningkatkan kinerja.

Dalam kasus kami, cukup banyak permintaan proxy yang dihasilkan, jadi saya menaikkan nilai ini untuk menerima lebih banyak permintaan sekaligus. Namun demikian, seperti yang dikatakan dalam dokumentasi, layak untuk diuji bahwa dalam mode multi-thread, beban didistribusikan secara merata di antara proses.

Semua parameter bersama-sama:

  tune.bufsize 16384 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432 

Yang tidak pernah terjadi adalah waktu habis. Apa yang akan kita lakukan tanpa mereka?


  • timeout connect - time untuk membuat koneksi dengan backend. Jika koneksi dengan backend tidak terlalu baik, maka lebih baik untuk menonaktifkannya dengan batas waktu ini sampai jaringan kembali normal.
  • timeout client - timeout untuk pengiriman byte data pertama. Ini membantu untuk memutuskan koneksi mereka yang membuat permintaan "dalam cadangan".

Kulstory tentang klien HTTP di Go
Go memiliki klien HTTP reguler yang memiliki kemampuan menjaga kumpulan koneksi ke server. Jadi satu cerita menarik terjadi, di mana batas waktu dan koneksi yang dijelaskan di atas dalam klien HTTP ikut serta. Setelah pengembang mengeluh bahwa ia secara berkala memiliki 408 kesalahan dari proxy. Kami melihat ke dalam kode klien dan melihat logika berikut di sana:

  • Kami mencoba untuk mengambil koneksi mapan gratis dari kolam;
  • jika tidak berhasil, mulai instalasi koneksi baru di goroutine;
  • periksa lagi kolam;
  • jika ada gratis di kolam renang - kami ambil, dan masukkan yang baru ke kolam, jika tidak - gunakan yang baru.

Sudah mengerti apa garamnya?

Jika klien telah membuat koneksi baru, tetapi belum menggunakannya, maka setelah lima detik server menutupnya, dan kasing berakhir. Klien menangkap ini hanya ketika sudah mendapatkan koneksi dari kolam dan mencoba menggunakannya. Patut diingat hal ini.

  • server batas waktu - waktu maksimum untuk menunggu respons dari server.
  • timeout client-fin / timeout server-fin - di sini kami melindungi diri dari koneksi setengah tertutup agar tidak menumpuknya di tabel sistem operasi.
  • timeout http-request adalah salah satu timeout yang paling cocok. Memungkinkan Anda memangkas klien yang lambat yang tidak dapat membuat permintaan HTTP dalam waktu yang ditentukan untuk mereka.
  • timeout http-keep-hidup - khususnya dalam kasus kami, jika koneksi keep-alive hang tanpa permintaan selama lebih dari 50 detik, maka kemungkinan besar ada sesuatu yang salah dan koneksi dapat ditutup, sehingga membebaskan memori untuk sesuatu yang baru cahaya.

Semua batas waktu bersama:

 defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s 

Penebangan Mengapa begitu sulit?


Seperti yang saya tulis sebelumnya, paling sering dalam keputusan saya, saya menggunakan Nginx, karena itu saya dimanjakan oleh sintaks dan kesederhanaan memodifikasi format log. Saya terutama menyukai fitur pembunuh - format log dalam bentuk json, kemudian menguraikannya dengan pustaka standar apa pun.

Apa yang kita miliki di Haproxy? Ada peluang seperti itu, hanya Anda yang dapat menulis secara eksklusif di syslog, dan sintaks konfigurasi sedikit lebih terbungkus.
Saya akan memberi Anda contoh konfigurasi dengan komentar:

 #  ,     ,    (   # error.log  nginx) log 127.0.0.1:2514 len 8192 local1 notice emerg #    -  access.log log 127.0.0.1:2514 len 8192 local7 info 

Nyeri tertentu disebabkan oleh saat-saat seperti:
  • nama variabel pendek, dan terutama kombinasinya seperti% HU atau% fp
  • format tidak dapat dibagi menjadi beberapa baris, jadi Anda harus menulis alas kaki dalam satu baris. sulit untuk menambah / menghapus item baru / tidak perlu
  • Agar beberapa variabel berfungsi, mereka harus secara eksplisit dinyatakan melalui tajuk permintaan tangkap

Alhasil, untuk mendapatkan sesuatu yang menarik, Anda harus menggunakan alas kaki seperti itu:

 log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}' 

Sepertinya hal-hal kecil, tapi menyenangkan


Saya menggambarkan format log di atas, tetapi tidak begitu sederhana. Untuk menyetor beberapa elemen di dalamnya, seperti:

  • http_host
  • http_referer,
  • http_user_agent,

pertama Anda perlu mengambil data ini dari permintaan ( capture ) dan memasukkannya ke dalam array nilai yang ditangkap.

Berikut ini sebuah contoh:

 capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 

Akibatnya, kita sekarang dapat mengakses elemen yang kita butuhkan dengan cara ini:
%[capture.req.hdr(N)] , di mana N adalah nomor urut dari definisi grup tangkap.
Dalam contoh di atas, header Host akan berada di nomor 0, dan User-Agent akan berada di nomor 2.

Haproxy memiliki kekhasan: ia menyelesaikan alamat DNS dari backend pada saat startup dan, jika tidak dapat menyelesaikan salah satu alamat, jatuh kematian si pemberani.

Dalam kasus kami, ini sangat tidak nyaman, karena ada banyak backend, kami tidak mengelolanya, dan lebih baik mendapatkan 503 dari Haproxy daripada seluruh server proxy akan menolak untuk memulai karena satu penyedia. Opsi berikut membantu kami dengan ini: init-addr .

Baris yang diambil langsung dari dokumentasi memungkinkan kami untuk menelusuri semua metode yang tersedia untuk menyelesaikan alamat dan, dalam kasus file, tunda saja masalah ini hingga nanti dan lanjutkan:

 default-server init-addr last,libc,none 

Dan akhirnya, favorit saya: seleksi backend.
Sintaks untuk konfigurasi pemilihan backend Haproxy akrab bagi semua orang:

 use_backend <backend1_name> if <condition1> use_backend <backend2_name> if <condition2> default-backend <backend3> 

Tapi, kata yang tepat, itu entah bagaimana tidak terlalu. Saya telah menjelaskan semua backend dengan cara otomatis (lihat artikel sebelumnya ), akan mungkin untuk menghasilkan use_backend sini use_backend , bisnis yang buruk tidak rumit, tetapi saya tidak mau. Akibatnya, cara lain ditemukan:

  capture request header Host len 32 capture request header Referer len 128 capture request header User-Agent len 128 #   host_present      Host acl host_present hdr(host) -m len gt 0 #    ,     use_backend %[req.hdr(host),lower,field(1,'.')] if host_present #      ,    default_backend default backend default mode http server no_server 127.0.0.1:65535 

Dengan demikian, kami menstandarkan nama backend dan url yang dengannya Anda dapat melihatnya.

Nah, sekarang kompilasi dari contoh di atas menjadi satu file:

Versi lengkap konfigurasi
  global daemon maxconn 524288 nbproc 4 cpu-map 1 0 cpu-map 2 1 cpu-map 3 2 cpu-map 4 3 tune.bufsize 16384 tune.comp.maxlevel 1 tune.http.cookielen 63 tune.http.maxhdr 101 tune.maxaccept 256 tune.rcvbuf.client 33554432 tune.rcvbuf.server 33554432 tune.sndbuf.client 33554432 tune.sndbuf.server 33554432 stats socket /run/haproxy.sock mode 600 level admin log /dev/stdout local0 debug defaults mode http maxconn 524288 timeout connect 5s timeout client 10s timeout server 120s timeout client-fin 1s timeout server-fin 1s timeout http-request 10s timeout http-keep-alive 50s default-server init-addr last,libc,none log 127.0.0.1:2514 len 8192 local1 notice emerg log 127.0.0.1:2514 len 8192 local7 info log-format '{"status":"%ST","bytes_read":"%B","bytes_uploaded":"%U","hostname":"%H","method":"%HM","request_uri":"%HU","handshake_time":"%Th","request_idle_time":"%Ti","request_time":"%TR","response_time":"%Tr","timestamp":"%Ts","client_ip":"%ci","client_port":"%cp","frontend_port":"%fp","http_request":"%r","ssl_ciphers":"%sslc","ssl_version":"%sslv","date_time":"%t","http_host":"%[capture.req.hdr(0)]","http_referer":"%[capture.req.hdr(1)]","http_user_agent":"%[capture.req.hdr(2)]"}' 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 backend default mode http server no_server 127.0.0.1:65535 resolvers dns hold valid 1s timeout retry 100ms nameserver dns1 127.0.0.1:53 


Terima kasih kepada mereka yang membaca sampai akhir. Namun, ini belum semuanya. Lain kali, kita akan melihat hal-hal tingkat rendah terkait dengan mengoptimalkan sistem itu sendiri, di mana Haproxy bekerja, sehingga ia dan sistem operasi kami merasa nyaman bersama, dan ada cukup zat besi untuk semua orang.

Sampai ketemu lagi!

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


All Articles