Vaakasuunnassa skaalattaviksi suunnitellut verkkosovellukset edellyttävät usein yhtä tai useampaa kuormituksen tasapainotussolmua. Niiden ensisijainen tarkoitus on jakaa saapuva liikenne kohtuullisella tavalla käytettävissä olevien verkkopalvelimien välillä. Kyky lisätä verkkosovelluksen kokonaiskapasiteettia yksinkertaisesti lisäämällä solmujen määrää ja antamalla kuormituksen tasapainotinten sopeutua tähän muutokseen voi osoittautua erittäin hyödylliseksi tuotannossa.
NGINX on verkkopalvelin, joka tarjoaa korkean suorituskyvyn kuormituksen tasausominaisuudet monien muiden ominaisuuksiensa lisäksi. Jotkut näistä ominaisuuksista ovat saatavana vain osana heidän tilausmalliaan, mutta ilmainen ja avoin lähdekoodiversio on silti erittäin monipuolinen ja mukana toimitetaan tärkeimmät kuormituksen tasausominaisuudet.
Tässä opetusohjelmassa tutkitaan kokeellinen työkalu jonka avulla voit määrittää NGINX-ilmentymän lennossa toimimaan kuormituksen tasapainottajana, jolloin kaikki NGINX-määritystiedostojen yksityiskohdat saadaan siistiksi tarjoamalla siisti verkkopohjainen käyttöliittymä. Tämän artikkelin tarkoituksena on osoittaa, kuinka helppoa on aloittaa tällaisen työkalun rakentaminen. On syytä mainita, että Loadcat-projekti on innoittamana vahvasti Linoden projektista NodeBalancers .
Yksi NGINX: n suosituimmista käyttötavoista on käänteinen välityspyyntö asiakkailta web-palvelinsovelluksiin. Vaikka ohjelmointikielillä, kuten Node.js ja Go, kehitetyt verkkosovellukset voivat olla omavaraisia verkkopalvelimia, käänteisen välityspalvelimen käyttö todellisen palvelinsovelluksen edessä tarjoaa lukuisia etuja. NGINX-määritystiedostossa olevan 'yksinkertaisen käyttötapauksen' palvelinlohko voi näyttää tältä:
server { listen 80; server_name example.com; location / { proxy_pass http://192.168.0.51:5000; } }
Tämä saisi NGINX: n kuuntelemaan portissa 80 kaikkia osoitteeseen example.com osoittavia pyyntöjä ja välittämään ne kaikki jollekin verkkopalvelinsovellukselle, joka toimii osoitteessa 192.168.0.51:5000. Voisimme myös käyttää loopback-IP-osoitetta 127.0.0.1 tässä, jos verkkosovelluspalvelin oli käynnissä paikallisesti. Huomaa, että yllä olevasta katkelmasta puuttuu joitain ilmeisiä säätöjä, joita käytetään usein käänteisen välityspalvelimen kokoonpanossa, mutta jota pidetään tällä tavalla lyhyyden vuoksi.
Mutta entä jos haluaisimme tasapainottaa kaikki saapuvat pyynnöt saman verkkosovelluspalvelimen kahden esiintymän välillä? Tällöin 'tuotantoketjun alkupäästä' annettu direktiivi on hyödyllinen. NGINX: ssä 'upstream' -direktiivin avulla on mahdollista määrittää useita taustan solmuja, joiden välillä NGINX tasapainottaa kaikki saapuvat pyynnöt. Esimerkiksi:
upstream nodes { server 192.168.0.51:5000; server 192.168.0.52:5000; } server { listen 80; server_name example.com; location / { proxy_pass http://nodes; } }
Huomaa, kuinka määritelimme kahdesta palvelimesta koostuvan 'ylävirran' lohkon, nimeltään 'solmut'. Jokainen palvelin tunnistetaan IP-osoitteen ja kuunteleman portin numeron perusteella. Tämän ansiosta NGINXistä tulee kuormituksen tasapainottaja yksinkertaisimmassa muodossaan. Oletuksena NGINX jakaa saapuvat pyynnöt pyöreällä tavalla, jolloin ensimmäinen välityspalvelin ensimmäiselle palvelimelle, toinen toiselle palvelimelle, kolmas ensimmäiselle palvelimelle ja niin edelleen.
NGINXillä on kuitenkin paljon enemmän tarjottavaa kuormituksen tasapainottamisessa. Sen avulla voit määrittää jokaiselle palvelimelle painot, merkitä ne väliaikaisesti poissa oleviksi, valita toisen tasapainotusalgoritmin (esim. On yksi, joka toimii asiakkaan IP-hash-pohjalta) jne. Nämä ominaisuudet ja määritysdirektiivit ovat kaikki hienosti dokumentoitu osoitteessa nginx.org . Lisäksi NGINX mahdollistaa kokoonpanotiedostojen muuttamisen ja lataamisen lennossa melkein ilman keskeytyksiä.
NGINX: n konfiguroitavuus ja yksinkertaiset määritystiedostot tekevät siitä todella helppoa mukauttaa moniin tarpeisiin. Ja lukuisia opetusohjelmia on jo olemassa Internetissä, joka opettaa sinulle kuinka NGINX määritetään kuormituksen tasapainottajaksi.
Ohjelmissa on jotain kiehtovaa, että sen sijaan, että tekisit jotain itse, määritä muut työkalut tekemään se heidän puolestaan. He eivät todellakaan tee paljon muuta kuin ehkä ottavat käyttäjän panoksia ja luovat muutaman tiedoston. Suurin osa hyödyistä, joita saat näistä työkaluista, ovat itse asiassa muiden työkalujen ominaisuuksia. Mutta ne varmasti tekevät elämästä helppoa. Kun yritin asettaa kuormituksen tasapainotinta omalle projektilleni, mietin: Mikset tekisi jotain vastaavaa NGINX: lle ja sen kuormituksen tasapainottamisominaisuuksille?
Loadcat on syntynyt!
Loadcat, rakennettu Mennä , on edelleen lapsenkengissään. Tällä hetkellä työkalun avulla voit määrittää NGINX: n vain kuormituksen tasapainottamista ja SSL: n lopettamista varten. Se tarjoaa yksinkertaisen verkkopohjaisen GUI käyttäjälle . Sen sijaan, että kävelisimme työkalun yksittäisten ominaisuuksien läpi, katsokaamme kurkista siihen, mitä sen alla on. Huomaa kuitenkin, että jos joku nauttii käsittelemästä NGINX-määritystiedostoja, hän saattaa löytää vähän arvoa tällaisesta työkalusta.
Go: n valitsemiseen ohjelmointikieleksi on muutama syy. Yksi niistä on se, että Go tuottaa käännettyjä binäärejä. Tämä antaa meille mahdollisuuden rakentaa ja jakaa tai ottaa käyttöön Loadcat käännettynä binaarina etäpalvelimille huolimatta riippuvuuksien ratkaisemisesta. Jotain, mikä yksinkertaistaa asennusprosessia huomattavasti. Binaari olettaa tietysti, että NGINX on jo asennettu ja sille on olemassa systemd-yksikkötiedosto.
Jos et ole a Mene insinööri , älä huoli ollenkaan. Go on melko helppoa ja hauskaa aloittaa. Lisäksi toteutus itsessään on hyvin yksinkertainen, ja sinun pitäisi pystyä seuraamaan mukana helposti.
Go build -työkalut asettavat muutamia rajoituksia sovelluksen jäsentämiselle ja jättävät loput kehittäjälle. Meidän tapauksessamme olemme hajottaneet asiat muutamaan Go-pakettiin niiden tarkoitusten perusteella:
Jos tarkastelemme tarkemmin pakettirakennetta, erityisesti kissan paketissa, huomaamme, että kaikki NGINX-spesifiset koodit on pidetty kissan / nginx-alipaketissa. Tämä tehdään, jotta voimme pitää loput sovelluslogiikasta yleisenä ja laajentaa tukea muille kuormituksen tasauslaitteille (esim. HAProxy) tulevaisuudessa.
Aloitetaan Loadcatin pääpaketista, joka löytyy kansiosta “cmd / loadcatd”. Päätoiminto, sovelluksen alkupiste, tekee kolme asiaa.
func main() { fconfig := flag.String('config', 'loadcat.conf', '') flag.Parse() cfg.LoadFile(*fconfig) feline.SetBase(filepath.Join(cfg.Current.Core.Dir, 'out')) data.OpenDB(filepath.Join(cfg.Current.Core.Dir, 'loadcat.db')) defer data.DB.Close() data.InitDB() http.Handle('/api', api.Router) http.Handle('/', ui.Router) go http.ListenAndServe(cfg.Current.Core.Address, nil) // Wait for an “interrupt“ signal (Ctrl+C in most terminals) }
Jotta asiat olisivat yksinkertaisia ja koodin lukeminen olisi helpompaa, kaikki virhekäsittelykoodit on poistettu yllä olevasta koodinpätkästä (ja myös tämän artikkelin katkelmista myöhemmin).
Kuten koodista käy ilmi, lataamme määritystiedostoa komentorivilipun ”-config” perusteella (joka on oletusarvoisesti ”loadcat.conf” nykyisessä hakemistossa). Seuraavaksi alustamme pari komponenttia, nimittäin kissan ydinpaketin ja tietokannan. Lopuksi aloitamme verkkopalvelimen verkkopohjaista käyttöliittymää varten.
Kokoonpanotiedoston lataaminen ja jäsentäminen on luultavasti helpoin osa tässä. Käytämme TOML: ää konfigurointitietojen koodaamiseen. Go: lle on saatavana siisti TOML-jäsennyspaketti. Tarvitsemme käyttäjältä hyvin vähän kokoonpanotietoja, ja useimmissa tapauksissa voimme määrittää järkevät oletusarvot näille arvoille. Seuraavat rakenne edustaa kokoonpanotiedoston rakennetta:
struct { Core struct { Address string Dir string Driver string } Nginx struct { Mode string Systemd struct { Service string } } }
Ja tässä voi näyttää tyypillinen “loadcat.conf” -tiedosto:
[core] address=':26590' dir='/var/lib/loadcat' driver='nginx' [nginx] mode='systemd' [nginx.systemd] service='nginx.service'
Kuten näemme, TOML-koodatun kokoonpanotiedoston ja rakenne sen yläpuolella. Kokoonpanopaketti aloittaa asettamalla järkeviä oletusarvoja tietyille kentille rakenne ja jäsentää sitten määritystiedoston sen päälle. Jos se ei löydä määritystiedostoa määritetyltä polulta, se luo sellaisen ja kaataa siihen oletusarvot ensin.
func LoadFile(name string) error { f, _ := os.Open(name) if os.IsNotExist(err) { f, _ = os.Create(name) toml.NewEncoder(f).Encode(Current) f.Close() return nil } toml.NewDecoder(f).Decode(&Current) return nil }
Tavata Pultti . Upotettu avain- / arvovarasto, joka on kirjoitettu puhtaana Go-muodossa. Se tulee pakettina, jolla on hyvin yksinkertainen sovellusliittymä, tukee tapahtumia heti ja on häiritsevästi nopeasti.
Pakettidatassa meillä on rakenteet edustavat kutakin tyyppiä. Meillä on esimerkiksi:
type Balancer struct { Id bson.ObjectId Label string Settings BalancerSettings } type Server struct { Id bson.ObjectId BalancerId bson.ObjectId Label string Settings ServerSettings }
… Missä esimerkiksi Tasapainottaja edustaa yhtä kuormituksen tasapainotinta. Loadcatin avulla voit tasapainottaa useiden verkkosovellusten pyynnöt yhden NGINX-ilmentymän kautta. Jokaisella tasapainottimella voi sitten olla yksi tai useampi palvelin sen takana, missä kukin palvelin voi olla erillinen taustasolmu.
Koska Bolt on avainarvosäilö eikä tue edistyneitä tietokantakyselyjä, meillä on sovelluspuolen logiikka, joka tekee tämän puolestamme. Loadcatia ei ole tarkoitettu määrittämään tuhansia tasapainottajaa, joissa kussakin on tuhansia palvelimia, joten luonnollisesti tämä naiivi lähestymistapa toimii hienosti. Bolt toimii myös avaimien ja arvojen kanssa, jotka ovat tavulohkoja, ja siksi BSON-koodaa rakenteet ennen kuin varastoit niitä Boltiin. Funktion toteutus, joka hakee luettelon Tasapainotin rakentuu tietokannasta näkyy alla:
func ListBalancers() ([]Balancer, error) { bals := []Balancer{} DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte('balancers')) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { bal := Balancer{} bson.Unmarshal(v, &bal) bals = append(bals, bal) } return nil }) return bals, nil }
ListBalancers toiminto käynnistää vain luku -tapahtuman, iteroi kaikki 'tasapainotin' -säilön avaimet ja arvot, dekoodaa jokaisen arvon Tasapainottimen rakenne ja palauttaa ne taulukossa.
Tasapainottimen säilyttäminen kauhassa on melkein yhtä helppoa:
func (l *Balancer) Put() error { if !l.Id.Valid() { l.Id = bson.NewObjectId() } if l.Label == '' { l.Label = 'Unlabelled' } if l.Settings.Protocol == 'https' { // Parse certificate details } else { // Clear fields relevant to HTTPS only, such as SSL options and certificate details } return DB.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte('balancers')) p, err := bson.Marshal(l) if err != nil { return err } return b.Put([]byte(l.Id.Hex()), p) }) }
Laittaa -toiminto määrittää joitain oletusarvoja tietyille kentille, jäsentää liitetyn SSL-varmenteen HTTPS-asetuksissa, aloittaa tapahtuman, koodaa rakenne ja tallentaa sen ämpäriin tasapainottimen tunnusta vastaan.
SSL-varmenteen jäsentämisen aikana kaksi tietoa puretaan käyttämällä vakiopakettikoodaus / pem ja tallennetaan SSL-vaihtoehdot alla asetukset kenttä: DNS-nimet ja sormenjälki.
Meillä on myös toiminto, joka etsii palvelimia tasapainottimen avulla:
func ListServersByBalancer(bal *Balancer) ([]Server, error) { srvs := []Server{} DB.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte('servers')) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { srv := Server{} bson.Unmarshal(v, &srv) if srv.BalancerId.Hex() != bal.Id.Hex() { continue } srvs = append(srvs, srv) } return nil }) return srvs, nil }
Tämä toiminto osoittaa, kuinka naiivi lähestymistapa todella on. Tässä luemme tehokkaasti koko 'palvelin' -säilön ja suodatamme merkityksettömät entiteetit ennen taulukon palauttamista. Mutta jälleen kerran, tämä toimii hienosti, eikä ole mitään todellista syytä muuttaa sitä.
Laittaa palvelimille on paljon yksinkertaisempi kuin Tasapainottimen rakenne koska se ei vaadi niin monta koodiasetusriviä ja laskettuja kenttiä.
Ennen Loadcatin käyttöä meidän on määritettävä NGINX lataamaan luodut kokoonpanotiedostot. Loadcat luo ”nginx.conf” -tiedoston jokaiselle tasauslaitteelle hakemiston alle tasapainottimen tunnuksella (lyhyt hex-merkkijono). Nämä hakemistot luodaan 'out' -hakemistoon osoitteessa cwd
. Siksi on tärkeää, että määrität NGINX: n lataamaan nämä luodut kokoonpanotiedostot. Tämä voidaan tehdä käyttämällä 'sisälly' -direktiiviä 'http' -lohkossa:
Muokkaa tiedostoa /etc/nginx/nginx.conf ja lisää seuraava rivi “http” -lohkon loppuun:
http { include /path/to/out/*/nginx.conf; }
Tämä saa NGINXin skannaamaan kaikki hakemistot, jotka löytyvät kohdasta / / polku / ulos / ulos /, etsimään tiedostoja nimeltä nginx.conf jokaisesta hakemistosta ja lataamaan kaikki löytämänsä hakemistot.
Määritämme käyttöliittymän ydinpaketissamme, feline Kuljettaja . Minkä tahansa rakenne joka tarjoaa kaksi toimintoa, Tuottaa ja Lataa uudelleen , jolla on oikea allekirjoitus, voidaan pitää kuljettajana.
type Driver interface { Generate(string, *data.Balancer) error Reload() error }
Esimerkiksi strukt Nginx kissan / nginx-paketin alla:
type Nginx struct { sync.Mutex Systemd *dbus.Conn } func (n Nginx) Generate(dir string, bal *data.Balancer) error { // Acquire a lock on n.Mutex, and release before return f, _ := os.Create(filepath.Join(dir, 'nginx.conf')) TplNginxConf.Execute(f, /* template parameters */) f.Close() if bal.Settings.Protocol == 'https' { // Dump private key and certificate to the output directory (so that Nginx can find them) } return nil } func (n Nginx) Reload() error { // Acquire a lock on n.Mutex, and release before return switch cfg.Current.Nginx.Mode { case 'systemd': if n.Systemd == nil { c, err := dbus.NewSystemdConnection() n.Systemd = c } ch := make(chan string) n.Systemd.ReloadUnit(cfg.Current.Nginx.Systemd.Service, 'replace', ch) <-ch return nil default: return errors.New('unknown Nginx mode') } }
Tuottaa voidaan kutsua merkkijonolla, joka sisältää polun ulostulohakemistoon ja osoittimen a: lle Tasapainottaja strukturoitu instanssi. Go tarjoaa tekstipohjille vakiopaketin, jota NGINX-ohjain käyttää lopullisen NGINX-määritystiedoston luomiseen. Malli koostuu 'ylävirran' lohkosta, jota seuraa 'palvelin' -lohko, joka syntyy tasapainottimen konfiguroinnin perusteella:
var TplNginxConf = template.Must(template.New('').Parse(` upstream {{.Balancer.Id.Hex}} { {{if eq .Balancer.Settings.Algorithm 'least-connections'}} least_conn; {{else if eq .Balancer.Settings.Algorithm 'source-ip'}} ip_hash; {{end}} {{range $srv := .Balancer.Servers}} server {{$srv.Settings.Address}} weight={{$srv.Settings.Weight}} {{if eq $srv.Settings.Availability 'available'}}{{else if eq $srv.Settings.Availability 'backup'}}backup{{else if eq $srv.Settings.Availability 'unavailable'}}down{{end}}; {{end}} } server { {{if eq .Balancer.Settings.Protocol 'http'}} listen {{.Balancer.Settings.Port}}; {{else if eq .Balancer.Settings.Protocol 'https'}} listen {{.Balancer.Settings.Port}} ssl; {{end}} server_name {{.Balancer.Settings.Hostname}}; {{if eq .Balancer.Settings.Protocol 'https'}} ssl on; ssl_certificate {{.Dir}}/server.crt; ssl_certificate_key {{.Dir}}/server.key; {{end}} location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://{{.Balancer.Id.Hex}}; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; } } `))
Lataa uudelleen on toinen toiminto päällä Nginx-rakenne mikä saa NGINXin lataamaan kokoonpanotiedostot uudelleen. Käytetty mekanismi perustuu siihen, miten Loadcat määritetään. Oletuksena se olettaa, että NGINX on systemd-palvelu, joka toimii nimellä nginx.service siten, että [sudo] systemd reload nginx.service
toimisi. Kuitenkin komentorivikomennon suorittamisen sijaan se muodostaa yhteyden järjestelmään D-väylän kautta käyttämällä paketti github.com/coreos/go-systemd/dbus .
Kun kaikki nämä komponentit ovat paikallaan, yhdistämme kaiken tavallisella Bootstrap-käyttöliittymällä.
Näihin perustoimintoihin riittää muutama yksinkertainen GET- ja POST-reitin käsittelijä:
GET /balancers GET /balancers/new POST /balancers/new GET /balancers/{id} GET /balancers/{id}/edit POST /balancers/{id}/edit GET /balancers/{id}/servers/new POST /balancers/{id}/servers/new GET /servers/{id} GET /servers/{id}/edit POST /servers/{id}/edit
Jokaisen yksittäisen reitin läpi käyminen ei ehkä ole mielenkiintoisin asia tässä, koska nämä ovat melkein CRUD-sivut. Voit ehdottomasti kurkistaa package ui-koodi nähdä, kuinka kunkin reitin käsittelijät on toteutettu.
Jokainen ohjaustoiminto on rutiini, joka joko:
Esimerkiksi:
func ServeServerNewForm(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bal, _ := data.GetBalancer(bson.ObjectIdHex(vars['id'])) TplServerNewForm.Execute(w, struct { Balancer *data.Balancer }{ Balancer: bal, }) } func HandleServerCreate(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) bal, _ := data.GetBalancer(bson.ObjectIdHex(vars['id'])) r.ParseForm() body := struct { Label string `schema:'label'` Settings struct { Address string `schema:'address'` } `schema:'settings'` }{} schema.NewDecoder().Decode(&body, r.PostForm) srv := data.Server{} srv.BalancerId = bal.Id srv.Label = body.Label srv.Settings.Address = body.Settings.Address srv.Put() feline.Commit(bal) http.Redirect(w, r, '/servers/'+srv.Id.Hex()+'/edit', http.StatusSeeOther) }
Kaikki ServeServerNewForm toiminto ei, se hakee tasapainottimen datakaupasta ja renderöi mallin, TplServerList tässä tapauksessa, joka hakee luettelon asiaankuuluvista palvelimista Palvelimet toiminto tasapainottimessa.
HandleServerCreate funktio, toisin kuin jäsentää saapuvan POST-hyötykuorman kehosta a rakenne ja käyttää näitä tietoja välittömän ja pysyvän uuden luomiseen Palvelimen rakenne tiedostopalvelimessa ennen paketin kissan käyttöä NGINX-määritystiedoston uudelleensuuntaamiseksi tasapainottimelle.
Kaikki sivumallit tallennetaan tiedostoon 'ui / templates.go', ja vastaavat HTML-tiedostot löytyvät hakemistosta 'ui / templates'.
Loadcatin käyttöönotto etäpalvelimelle tai jopa paikallisessa ympäristössä on erittäin helppoa. Jos käytössäsi on Linux (64-bittinen), voit napata arkiston, jossa on valmiiksi rakennettu Loadcat-binaari, arkiston Tiedotteet-osio . Jos tunnet itsesi hieman seikkailunhaluiseksi, voit kloonata arkiston ja koota koodin itse. Vaikka kokemus siinä tapauksessa voi olla vähän pettymys Go-ohjelmien kokoaminen ei todellakaan ole haaste. Ja jos käytät Arch Linuxia, olet onnea! Jakelua varten on rakennettu paketti mukavuuden vuoksi. Yksinkertaisesti lataa se ja asenna se paketinhallinnan avulla. Asiaan liittyvät vaiheet on kuvattu tarkemmin projektissa README.md-tiedosto .
Kun olet määrittänyt ja suorittanut Loadcatin, osoita verkkoselaimesi kohtaan “http: // localhost: 26590” (olettaen, että se toimii paikallisesti ja kuuntelee portissa 26590). Seuraavaksi luo tasapainotin, luo pari palvelinta, varmista, että jokin kuuntelee määriteltyjä portteja, ja voila sinulla pitäisi olla NGINX-kuormituksen tasapainon saapuvat pyynnöt käynnissä olevien palvelinten välillä.
Tämä työkalu on kaukana täydellisestä, ja itse asiassa se on melko kokeellinen projekti. Työkalu ei kata edes kaikkia NGINX: n perustoimintoja. Esimerkiksi, jos haluat tallentaa välimuistiin resursseja, joita palvelee NGINX-tason taustasolmut, sinun on silti muokattava NGINX-määritystiedostoja käsin. Ja se tekee asioista jännittäviä. Täällä voidaan tehdä paljon, ja se on juuri seuraava: se kattaa vielä enemmän NGINX: n kuormituksen tasapainotustoimintoja - perusominaisuudet ja todennäköisesti jopa ne, joita NGINX Plus tarjoaa.
Kokeile Loadcatia. Tarkista koodi, haarauta se, vaihda se, pelaa sillä. Kerro meille myös, jos olet rakentanut työkalun, joka määrittää muita ohjelmistoja, tai olet käyttänyt sitä, josta pidät, alla olevassa kommenttiosassa.