水平方向にスケーラブルになるように設計されたWebアプリケーションでは、多くの場合、1つ以上の負荷分散ノードが必要です。それらの主な目的は、受信トラフィックを利用可能なWebサーバー間で公平に分散することです。ノードの数を増やし、ロードバランサーをこの変更に適応させるだけで、Webアプリケーションの全体的な容量を増やす機能は、本番環境で非常に役立つことがわかります。
NGINXは、他の多くの機能の中でも、高性能の負荷分散機能を提供するWebサーバーです。これらの機能の一部はサブスクリプションモデルの一部としてのみ利用可能ですが、無料のオープンソースバージョンは依然として非常に機能が豊富で、箱から出して最も重要な負荷分散機能が付属しています。
このチュートリアルでは、内部の仕組みを探ります。 実験ツール これにより、NGINXインスタンスをその場で構成してロードバランサーとして機能させ、きちんとしたWebベースのユーザーインターフェイスを提供することで、NGINX構成ファイルの本質的な詳細をすべて抽象化できます。この記事の目的は、そのようなツールの作成を開始するのがいかに簡単かを示すことです。プロジェクトLoadcatはLinodeのに大きく影響を受けていることは言及する価値があります NodeBalancers 。
NGINXの最も一般的な使用法の1つは、クライアントからWebサーバーアプリケーションへのリクエストのリバースプロキシです。 Node.jsやGoなどのプログラミング言語で開発されたWebアプリケーションは、自給自足のWebサーバーですが、実際のサーバーアプリケーションの前にリバースプロキシを配置すると、多くの利点があります。 NGINX構成ファイルのこのような単純なユースケースの「サーバー」ブロックは、次のようになります。
server { listen 80; server_name example.com; location / { proxy_pass http://192.168.0.51:5000; } }
これにより、NGINXはexample.comを指すすべてのリクエストをポート80でリッスンし、それぞれを192.168.0.51:5000で実行されているWebサーバーアプリケーションに渡します。 Webアプリケーションサーバーがローカルで実行されている場合は、ここでループバックIPアドレス127.0.0.1を使用することもできます。上記のスニペットには、リバースプロキシ構成でよく使用される明らかな調整がいくつか欠けていることに注意してください。ただし、簡潔にするためにこのように維持されています。
クレジットカード番号の取得方法
しかし、同じWebアプリケーションサーバーの2つのインスタンス間ですべての着信要求のバランスを取りたい場合はどうでしょうか。ここで「アップストリーム」ディレクティブが役立ちます。 NGINXでは、「アップストリーム」ディレクティブを使用して、NGINXがすべての着信リクエストのバランスを取る複数のバックエンドノードを定義できます。例えば:
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; } }
2つのサーバーで構成される「ノード」という名前の「アップストリーム」ブロックをどのように定義したかに注目してください。各サーバーは、リッスンしているIPアドレスとポート番号によって識別されます。これにより、NGINXは最も単純な形式のロードバランサーになります。デフォルトでは、NGINXはラウンドロビン方式で着信リクエストを配信します。最初のリクエストは最初のサーバーにプロキシされ、2番目のリクエストは2番目のサーバーにプロキシされ、3番目のリクエストは最初のサーバーにプロキシされます。
ただし、負荷分散に関しては、NGINXにはさらに多くの機能があります。これにより、各サーバーの重みを定義したり、一時的に使用不可としてマークしたり、別のバランシングアルゴリズムを選択したりできます(たとえば、クライアントのIPハッシュに基づいて機能するアルゴリズムがあります)。これらの機能と構成ディレクティブはすべて nginx.orgでうまく文書化されています 。さらに、NGINXを使用すると、ほとんど中断することなく、構成ファイルをオンザフライで変更および再ロードできます。
NGINXの構成可能性とシンプルな構成ファイルにより、NGINXを多くのニーズに簡単に適応させることができます。そして、たくさんのチュートリアル すでに存在しています NGINXをロードバランサーとして構成する方法を正確に教えるインターネット上。
プログラムには、自分で何かをする代わりに、他のツールを構成してそれを実行するという魅力的なものがあります。ユーザー入力を受け取っていくつかのファイルを生成する以外は、実際にはあまり機能しません。これらのツールから得られるメリットのほとんどは、実際には他のツールの機能です。しかし、彼らは確かに人生を楽にします。自分のプロジェクトの1つにロードバランサーをセットアップしようとしているときに、NGINXとその負荷分散機能に似たようなことをしてみませんか?
ロードキャット 生まれました!
Loadcat、で構築 行く 、まだ初期段階です。現時点では、このツールを使用すると、負荷分散とSSL終了のみを目的としてNGINXを構成できます。シンプルなWebベースを提供します ユーザーのためのGUI 。ツールの個々の機能をウォークスルーする代わりに、その下にあるものを覗いてみましょう。ただし、誰かがNGINX構成ファイルを手作業で操作することを楽しんでいる場合、そのようなツールにはほとんど価値がない可能性があることに注意してください。
このためのプログラミング言語としてGoを選択する理由はいくつかあります。それらの1つは、Goがコンパイル済みバイナリを生成することです。これにより、依存関係の解決を心配することなく、Loadcatをコンパイル済みバイナリとしてビルドし、リモートサーバーに配布またはデプロイできます。セットアッププロセスを大幅に簡素化するもの。もちろん、バイナリはNGINXがすでにインストールされており、systemdユニットファイルが存在することを前提としています。
あなたがいない場合 エンジニアに行く 、全く心配しないでください。 Go is とても簡単で楽しい 始めるために。さらに、実装自体は非常に単純であり、簡単に実行できるはずです。
Goビルドツールは、アプリケーションを構造化する方法にいくつかの制限を課し、残りは開発者に任せます。私たちの場合、目的に基づいていくつかのGoパッケージに分割しました。
特に猫のパッケージ内のパッケージ構造を詳しく見ると、すべてのNGINX固有のコードがサブパッケージの猫/ nginx内に保持されていることがわかります。これは、アプリケーションロジックの残りの部分を汎用的に保ち、将来的に他のロードバランサー(HAProxyなど)のサポートを拡張できるようにするために行われます。
「cmd / loadcatd」内にあるLoadcatのメインパッケージから始めましょう。アプリケーションのエントリポイントである主な機能は、3つのことを行います。
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) }
物事をシンプルに保ち、コードを読みやすくするために、すべてのエラー処理コードが上記のスニペットから(およびこの記事の後半のスニペットからも)削除されました。
コードからわかるように、「-config」コマンドラインフラグ(現在のディレクトリではデフォルトで「loadcat.conf」)に基づいて構成ファイルをロードしています。次に、いくつかのコンポーネント、つまりコアネコパッケージとデータベースを初期化します。最後に、WebベースのGUI用のWebサーバーを起動します。
ここでは、構成ファイルのロードと解析がおそらく最も簡単な部分です。 TOMLを使用して構成情報をエンコードしています。 Goで利用できるきちんとしたTOML解析パッケージがあります。ユーザーからの構成情報はほとんど必要ありません。ほとんどの場合、これらの値の適切なデフォルトを決定できます。以下 構造体 構成ファイルの構造を表します。
struct { Core struct { Address string Dir string Driver string } Nginx struct { Mode string Systemd struct { Service string } } }
そして、典型的な「loadcat.conf」ファイルは次のようになります。
[core] address=':26590' dir='/var/lib/loadcat' driver='nginx' [nginx] mode='systemd' [nginx.systemd] service='nginx.service'
ご覧のとおり、TOMLでエンコードされた構成ファイルの構造と 構造体 その上に示されています。構成パッケージは、の特定のフィールドにいくつかの適切なデフォルトを設定することから始まります。 構造体 次に、その上で構成ファイルを解析します。指定されたパスで構成ファイルが見つからない場合は、構成ファイルを作成し、最初にデフォルト値をダンプします。
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 }
会う ボルト 。純粋なGoで記述された組み込みのKey / Valueストア。非常にシンプルなAPIを備えたパッケージとして提供され、箱から出してすぐにトランザクションをサポートします。 不穏に 速い。
パッケージデータ内には、 構造体 各タイプのエンティティを表します。たとえば、次のようになります。
type Balancer struct { Id bson.ObjectId Label string Settings BalancerSettings } type Server struct { Id bson.ObjectId BalancerId bson.ObjectId Label string Settings ServerSettings }
…ここで、 バランサー 単一のロードバランサーを表します。 Loadcatを使用すると、NGINXの単一インスタンスを介して複数のWebアプリケーションのリクエストのバランスを効果的にとることができます。その後、すべてのバランサーの背後に1つ以上のサーバーを配置でき、各サーバーを個別のバックエンドノードにすることができます。
Boltはキー値ストアであり、高度なデータベースクエリをサポートしていないため、これを行うアプリケーション側のロジックがあります。 Loadcatは、それぞれに数千のサーバーを備えた数千のバランサーを構成するためのものではないため、当然、この単純なアプローチは問題なく機能します。また、Boltはバイトスライスであるキーと値を処理するため、BSONでエンコードします。 構造体 それらをボルトに保管する前に。のリストを取得する関数の実装 バランサー構造体 データベースからの情報を以下に示します。
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 関数は読み取り専用トランザクションを開始し、「バランサー」バケット内のすべてのキーと値を反復処理し、各値を次のインスタンスにデコードします。 バランサー構造 そしてそれらを配列で返します。
cssメディアクエリブレークポイントはに基づいている必要があります
バランサーをバケットに保管するのもほぼ同じくらい簡単です。
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) }) }
ザ・ プット 関数は、特定のフィールドにいくつかのデフォルト値を割り当て、HTTPSセットアップで添付されたSSL証明書を解析し、トランザクションを開始し、エンコードします。 構造体 インスタンスを作成し、バランサーのIDに対してバケットに保存します。
SSL証明書の解析中に、2つの情報が 標準パッケージエンコーディング/ pem に保存されます SSLOptions 下 設定 フィールド:DNS名とフィンガープリント。
バランサーでサーバーを検索する機能もあります。
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 }
この関数は、私たちのアプローチが実際にどれほど素朴であるかを示しています。ここでは、「サーバー」バケット全体を効果的に読み取り、配列を返す前に無関係なエンティティを除外しています。しかし、繰り返しになりますが、これは問題なく機能し、変更する本当の理由はありません。
ザ・ プット サーバーの機能はそれよりもはるかに簡単です バランサー構造 デフォルトと計算フィールドを設定するコード行をそれほど必要としないためです。
Loadcatを使用する前に、生成された構成ファイルをロードするようにNGINXを構成する必要があります。 Loadcatは、バランサーのID(短い16進文字列)によってディレクトリの下に各バランサーの「nginx.conf」ファイルを生成します。これらのディレクトリは、cwd
の「out」ディレクトリの下に作成されます。したがって、これらの生成された構成ファイルをロードするようにNGINXを構成することが重要です。これは、「http」ブロック内の「include」ディレクティブを使用して実行できます。
/etc/nginx/nginx.confを編集し、「http」ブロックの最後に次の行を追加します。
http { include /path/to/out/*/nginx.conf; }
これにより、NGINXは「/ path / to / out /」の下にあるすべてのディレクトリをスキャンし、各ディレクトリ内で「nginx.conf」という名前のファイルを探し、見つかった各ディレクトリを読み込みます。
コアパッケージである猫では、インターフェースを定義します 運転者 。どれか 構造体 2つの機能を提供します。 生む そして リロード 、正しい署名があれば、ドライバーとしての資格があります。
type Driver interface { Generate(string, *data.Balancer) error Reload() error }
たとえば、構造体 Nginx feline / nginxパッケージの下:
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') } }
生む 出力ディレクトリへのパスとへのポインタを含む文字列で呼び出すことができます バランサー 構造体インスタンス。 Goは、テキストテンプレートの標準パッケージを提供します。これは、NGINXドライバーが最終的なNGINX構成ファイルを生成するために使用します。テンプレートは、バランサーの構成方法に基づいて生成された「アップストリーム」ブロックとそれに続く「サーバー」ブロックで構成されます。
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'; } } `))
リロード 上の他の機能です Nginx構造体 これにより、NGINXは構成ファイルをリロードします。使用されるメカニズムは、Loadcatの構成方法に基づいています。デフォルトでは、NGINXはnginx.serviceとして実行されるsystemdサービスであり、[sudo] systemd reload nginx.service
のようになります。うまくいくだろう。ただし、シェルコマンドを実行する代わりに、D-Busを介してsystemdへの接続を確立します。 パッケージgithub.com/coreos/go-systemd/dbus 。
これらすべてのコンポーネントを配置したら、プレーンなBootstrapユーザーインターフェイスですべてをまとめます。
これらの基本的な機能については、いくつかの単純なGETおよびPOSTルートハンドラーで十分です。
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
これらはほとんどCRUDページであるため、個々のルートを通過することは、ここで行うのが最も興味深いことではない場合があります。絶対に気軽に覗いてみてください package ui code これらの各ルートのハンドラーがどのように実装されているかを確認します。
クレジットカードの年齢確認をバイパスする方法
各ハンドラー関数は、次のいずれかのルーチンです。
例えば:
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) }
すべて ServeServerNewForm 関数は、データストアからバランサーをフェッチしてテンプレートをレンダリングします。 TplServerList この場合、を使用して関連するサーバーのリストを取得します サーバー バランサーの機能。
HandleServerCreate 一方、関数は、本体からの着信POSTペイロードを解析して 構造体 これらのデータを使用して、新しいデータをインスタンス化して永続化します サーバー構造 パッケージfelineを使用してバランサーのNGINX構成ファイルを再生成する前に、データストアで。
すべてのページテンプレートは「ui / templates.go」ファイルに保存され、対応するテンプレートHTMLファイルは「ui / templates」ディレクトリにあります。
Loadcatをリモートサーバーまたはローカル環境にデプロイするのは非常に簡単です。 Linux(64ビット)を実行している場合は、リポジトリからビルド済みのLoadcatバイナリを含むアーカイブを取得できます。 リリースセクション 。少し冒険心がある場合は、リポジトリのクローンを作成して、自分でコードをコンパイルできます。ただし、その場合の経験は少しかもしれません がっかり Goプログラムのコンパイルは実際には難しいことではありません。そして、Arch Linuxを実行している場合は、幸運です。配布用のパッケージが作成されています。単に ダウンロードする パッケージマネージャーを使用してインストールします。関連する手順は、プロジェクトの README.mdファイル 。
Loadcatを構成して実行したら、Webブラウザーで「http:// localhost:26590」を指定します(ローカルで実行され、ポート26590でリッスンしていると想定します)。次に、バランサーを作成し、いくつかのサーバーを作成し、定義されたポートで何かがリッスンしていることを確認します。実行中のサーバー間でNGINXが着信リクエストの負荷を分散する必要があります。
このツールは完璧にはほど遠いものであり、実際、かなり実験的なプロジェクトです。このツールは、NGINXのすべての基本機能を網羅しているわけではありません。たとえば、NGINXレイヤーのバックエンドノードによって提供されるアセットをキャッシュする場合でも、NGINX構成ファイルを手動で変更する必要があります。そしてそれが物事をエキサイティングにするものです。ここでできることはたくさんあり、それがまさに次のことです。NGINXの負荷分散機能のさらに多くをカバーします。基本的な機能であり、おそらくNGINXPlusが提供する機能ですらあります。
Loadcatを試してみてください。コードをチェックして、フォークして、変更して、遊んでください。また、他のソフトウェアを構成するツールを作成したか、または下のコメントセクションで本当に気に入ったツールを使用したかどうかをお知らせください。