HAProxyとIP Sharingを用いた冗長化ロードバランサーの作成

## はじめに
HAProxyは高可用性・負荷分散を実現するためのロードバランサー機能をもったオープンソースソフトウェアです。大規模なHTTPやその他TCPアプリケーションベースの負荷分散にも用いられています。Akamaiのcloud computing servicesで提供されているLinodeインスタンスに、IP Sharing機能を使うことで、高可用性のWebサイトを構成することができます。

## NodeBalancersとの使い分け
Akamaiのcloud computing servicesでは、ManagedのロードバランサーであるNodeBalancersも用意されています。それぞれ以下の特徴がありますので構築するシステムに合わせて選択してください。

### NodeBalancers
- Managedサービスのためインフラの運用をAkamaiに任せることができる
- GUIでの設定画面を利用でき、簡単に冗長化されたロードバランサーを構築することができる
- ステータス画面もGUIで簡単に確認することができる


NodeBalancersの仕様は以下を参照してください

https://www.linode.com/docs/products/networking/nodebalancers/


### HAProxyなどのLinuxロードバランサー
- システムに合わせて柔軟にロードバランサーを構成することができる
- 詳細なアクセスログなど細かい情報を取得することができる
- Linodeインスタンス上に構築するため、Cloud Firewallにて特定の通信のみを制御することができる



## 本記事での構成

本記事では以下の構成で構築しています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/314eeab8-a57c-8269-d083-cb713f49cf9e.png)


- HAProxyをロードバランサーとして構成（以下HAProxyサーバー）
- 2台のHAProxyサーバーをIP SharingにてActive/Standby構成
- Webサーバは3台構成
- ネットワークはすべてPublic IPにて構成
- HAProxyサーバーは証明書を取得し、SSLにてクライアントからの通信を処理
- HAProxyサーバーはSSLオフロードを行い、WebサーバーへはHTTPに切り替えて接続
- 負荷分散方式はラウンドロビンおよびHAProxyにてCookieを付与しセッションの永続性を適用
- それぞれのLinodeインスタンスにCloud Firewallを適用
- （オプション）AkamaiのWeb Secuirty/CDNサービス(Akamai Intelligent Edge Platform)を適用の際、Cloud FirewallへSite ShieldのIPアドレスリストを適用




## keepalivedのインストールと設定
HAProxyサーバーにIP Sharing及び、冗長化設定を行います。
IP Sharingを用いたLinodeインスタンスの冗長化については、KeepAlivedなどの方式を用いる必要があります。設定方法については以下の記事を参考にしてください。なお本記事では東京リージョンで現時点でサポートしているARPベースでのKeepAlived方式を利用しています。（2023年7月現在、東京リージョンはBGP方式へのアップグレード中です）

https://qiita.com/kabe-akam/items/5e1ef751b8badf454f94


HAProxyサーバーの冗長化に対応させるために、Keepalivedのコンフィグを少しだけ変更します。
check_haproxyを追加しますが、他は上記の記事と同じです。
```
$ sudo vi /etc/keepalived/keepalived.conf
```

```bash:/etc/keepalived/keepalived.conf
global_defs {
}

vrrp_script check_haproxy {   ## HAProxy用にcheck_haproxyを追加
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance Instance1 {
    state MASTER
    interface eth0
    virtual_router_id 10
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass fowebpass    #fowebpassは例。共通のパスワードを記載
    }
    unicast_src_ip 198.51.100.1 #左記は例。サーバーAの1つ目のPublic IPを記載
    unicast_peer {
       198.51.100.2   #左記は例。サーバーBのPublic IPを記載
    }
    virtual_ipaddress {
       198.51.100.3   #左記は例。IP Sharingで割り当てたIPを記載
    }
    track_script {
      check_haproxy   ## HAProxy用にcheck_haproxyを追加
    }
}
```

2台目のHAProxyサーバーにも設定を適用します。
```bash:/etc/keepalived/keepalived.conf
global_defs {
}

vrrp_script check_haproxy {   ## HAProxy用にcheck_haproxyを追加
    script "killall -0 haproxy"
    interval 2
    weight 2
}

vrrp_instance Instance1 {
    state BACKUP    # サーバーBはBACKUPに変更
    interface eth0
    virtual_router_id 10
    priority 99    # サーバーBは99に変更
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass fowebpass    #fowebpassは例。共通のパスワードを記載
    }
    unicast_src_ip 198.51.100.2 #左記は例。サーバーBのPublic IPを記載
    unicast_peer {
       198.51.100.1   #左記は例。サーバーAの1つ目のPublic IPを記載
    }
    virtual_ipaddress {
       198.51.100.3   #左記は例。IP Sharingで割り当てたIPを記載
    }
    track_script {
      check_haproxy   ## HAProxy用にcheck_haproxyを追加
    }
}

```




## 証明書の取得
HAProxyサーバー用にサーバー証明書を取得し適用します。ここではLet's Encryptのサーバー証明書を取得します。手順については多くの方法がありますが、以下の記事にて紹介しておりますのでご参照ください。

https://qiita.com/kabe-akam/items/16d38c96d660d1db6d92#%E3%82%B5%E3%83%BC%E3%83%90%E8%A8%BC%E6%98%8E%E6%9B%B8%E3%81%AE%E5%8F%96%E5%BE%97


取得したサーバー証明書、プライベートキーを一つのファイルにまとめて、/etc/ssl/private配下に保存しています。 [FQDN]は構築するWebサイトのFQDNに置き換えます。
```
$ sudo cat /etc/letsencrypt/live/[FQDN]/fullchain.pem /etc/letsencrypt/live/[FQDN]/privkey.pem | tee /etc/ssl/private/[FQDN].pem
```
作成した/etc/ssl/private/[FQDN].pem を2台目のHAProxyにもコピーして保存します。




## HAProxyのインストールと設定
2台のHAProxyサーバーに、haproxyソフトウェアをインストールし設定を行います。

haproxyのインストールを行います。SSLも利用しますのでopensslも一緒にインストールします。
```
$ sudo apt update -y
$ sudo apt install -y haproxy openssl
```

haproxy.cfgを修正します。
```
$ sudo vi /etc/haproxy/haproxy.cfg
```

haproxy.cfgファイル文末に以下を追記します。maxconnなどHAProxyのパラメーターについては構築するシステムに併せて修正してください。2台とも同じ設定を行います。
```bash:/etc/haproxy/haproxy.cfg
<文末>

frontend front_in
    #bind [共有IP]:80   # HTTPでのテスト用
    #bind 個別のPublic IP:443 ssl crt /etc/ssl/private/[FQDN].pem   #IP Sharing未使用でのテスト用
    ##  SSL binding ##
    bind [共有IP]:443 ssl crt /etc/ssl/private/[FQDN].pem
    maxconn 30000
    default_backend web_backend

backend web_backend
    mode http
    balance roundrobin
    cookie lbsessionid insert nocache indirect preserve httponly secure maxidle 30m maxlife 8h
    # HTTPでのテスト時はsecure設定を外す
    # cookie lbsessionid insert nocache indirect preserve httponly maxidle 30m maxlife 8h 
    server web1 [web01のpublic IP]:80 maxconn 30000 check cookie w01 inter 2000 rise 2 fall 5
    server web2 [web02のpublic IP]:80 maxconn 30000 check cookie w02 inter 2000 rise 2 fall 5
    server web3 [web03のpublic IP]:80 maxconn 30000 check cookie w03 inter 2000 rise 2 fall 5
    option forwardfor
    option httpchk GET /server.html   #死活監視用のターゲットコンテンツ。ここではserver.htmlを指定

listen stats
   bind  :8222 ssl crt /etc/ssl/private/[FQDN].pem
   #mode http   # HTTPでのテスト用
   stats enable
   stats uri /stats
   stats hide-version
   stats show-node
   stats auth USER:PASSWORD  # HAProxyのステータス画面のユーザー名およびパスワード
```




HAProxyサービスを起動します。
```
$ sudo systemctl start haproxy
$ sudo systemctl enable haproxy
```

## Webサーバーの準備
3台のWebサーバーを準備します。ここではシンプルなserver.htmlを準備して、どのサーバーに接続されているかわかりやすくしています。Webサーバーの構築については割愛します。

```bash:/var/www/html/server.html
<html><head></head><body>
<h1>Web-01</h1>
</html>
```


## Webサーバーの冗長化確認
最後に、接続するFQDNのDNSレコードを追加し接続を行います。DNSサーバーの設定については保持しているドメインにより異なりますので割愛します。AレコードのIPアドレスはIP Sharingで割り当てられたHAProxyの共有IPアドレスです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/4044e497-eb3c-fac5-80d9-bfa445b1453b.png)



作成したWebサイトのURLにアクセスします。
https://[FQDN]/server.html

ラウンドロビンでランダムに割り振られて、セッション永続性のためのCookieが付与されます。ここではWeb01にリクエストが振られました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/dcf5d924-eb0f-3eb1-a4a3-e687b22937ee.png)


haproxy.cfgで指定したCookieが付与されています。
```bash: haproxy.cfgの抜粋
　#この行にて、lbsessionidというCookieを付与しています。
  cookie lbsessionid insert nocache indirect preserve httponly secure maxidle 30m maxlife 8h
```
そのCookieを用いて、セッション永続性が機能していることがわかります。
（スクリーンショットはChromeブラウザのEditThiscookie拡張機能を用いて表示しています）
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/68af1fea-3301-4c99-92cf-79faa0d1b069.png" width=70%>



なおHAProxyの上では以下のようなアクセスログが確認できます。
```
Jul  4 02:01:52 localhost haproxy[32097]: 198.51.10.1:1562 [04/Jul/2023:04:01:52.422] front_in~ web_backend/web1 0/0/5/1/6 304 187 - - --VU 1/1/0/0/0 0/0 "GET /server.html HTTP/1.1"
```



Web01をシャットダウンしてから再度アクセスしてみます。今度はWeb02にリクエストが切り替わりました。HAProxyにてHTTP GETによるコンテンツ応答確認を行っていますので、プロセスが落ちてしまった場合でも切り替わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/ae1f5e07-0be1-89ce-7861-1e5ce4ba77b5.png)

Cookieも同様にweb02に変更されています。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/b2e5dd28-9a24-58be-7145-87b95a4d1ba6.png" width=70%>





HAProxyの「listen stats」項で設定しているポートにてHAproxyの接続状況及びWebサーバーのステータスを可視化することができます。
https://[FQDN]:8222/stats

Web01に接続したときのステータス画面です。Web01でセッションがカウントアップしていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/d7cc2003-2732-1813-997c-9aff36b5c25b.png)



Web01をシャットダウンした後のステータス画面です。Web01がダウン状態となり、かつWeb02のセッションがカウントアップしていることがわかります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/ca5ea7e5-a583-06e4-083d-20cb423bb2b8.png)




:::note
なおAkamai CDN を手前に配置するケースでは セキュリティ強化のため 443 ポートで受け付ける必要があるため、CDN 上で ステータス用のパスに対してオリジンにフォワードするポートを 8222 に設定してください。
:::

## HAProxyの冗長化確認
続いて、HAProxyサーバーの冗長化を確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/7297d8bc-a112-283c-f057-304036296104.png)


まずは通常時の構成です。1台目のhaproxy01にて処理されていることがわかります。（※事前にOSのホスト名設定が必要です）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/84511795-5a29-235a-fb04-0f0a76bd04bb.png)


1台目のHAProxyサーバーをシャットダウンしてみます。しばらく待つと今度は「haproxy02」で処理されていることが確認できます。KeepAlivedにてHAProxyのプロセスが落ちてしまった場合でも切り替わります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/1d7d9a64-5426-48e7-ef41-4390656ae8f6.png)


割り当てられた共有IPの移動および、ログレベルでのKeepalivedでの状態遷移はコマンドにて確認可能です。詳細については[IP Sharing](https://qiita.com/kabe-akam/items/5e1ef751b8badf454f94)の記事を確認してください。


## Cloud Firewallの設定
Akamaiのcloud computing servicesで提供されているCloud FirewallにてLinodeインスタンスへのアクセス制御を行うことが可能です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/29424d68-2a5e-7331-324a-342baa8a1ae6.png)


Cloud Firewallの設定方法については以下の画面をご参考ください。

https://www.linode.com/docs/products/networking/cloud-firewall/


### haproxy-lb Firewallルール
まずは1つ目のFirewallを作成してルールを適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/7cddd461-07ec-8119-60e5-d110587704e5.png)
作成したルールを、2台のHAProxyサーバーへ適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/a1574014-20ec-9b2c-3ea7-e87f8513cb54.png)　

### haproxy-web Firewallルール
続いて2つ目のFiwarellを作成してルールを適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/8a88dc95-6eef-d5f1-6f32-afbe583c957e.png)
作成したルールを、3台のWebサーバーへ適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/d6c18425-a175-c4ba-5327-2d2761daa34c.png)

以上により、それぞれのLinodeインスタンスでアクセス制御を行うことができました。


## Site Shieldの設定
AkamaiのWeb Secuirty/CDNサービスを併用する場合、AkamaiからのIPアドレスのみを許可することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/eca43e2a-5636-ae95-caa8-50005540ae0d.png)

SiteShieldの詳細については以下をご参考ください。

https://community.akamai.com/customers/s/article/JapanUserGroupSiteShield20190627090617?language=en_US


Site Shieldで利用されるIPアドレスリストをFirewallのルールに適用するだけでアクセス制御が完了します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2821330/33196edf-d6a6-4f00-40e1-21e0ffab3fc1.png)


## まとめ
Akamaiのcloud computing servicesではManagedのロードバランサーも提供されておりますが、Linuxで稼働するロードバランサーソフトウェアでもサービスの冗長と分散を行うことができます。ぜひ、システムの要件に合わせて最適なソリューションを検討してください。