RHEL 8 での HA Proxy のインストール

#1.環境 

RHEL 8.2 を使用しました。

```
# cat /etc/redhat-release 
Red Hat Enterprise Linux release 8.2 (Ootpa)
#
```

#2.インストール
```
yum -y  install haproxy
```

もしくは

```
dnf -y  install haproxy
```

これだけです。

#3.作業に必要な基本コマンド

`HA Proxy`は、`systemctl`で操作できるので、基本操作は大体想像通りです。
作業時のコピペ用に、良く使うコマンドをリストしておきます。

```
# 起動  
$ systemctl start haproxy

# 停止
$ systemctl stop haproxy

# 設定のリロード
$ systemctl reload haproxy

# ステータスの確認
$ systemctl status haproxy

#自動起動
$ systemctl enable haproxy

#自動起動設定確認
$ systemctl is-enabled haproxy

# haproxy.cfg の書式確認 (書いた設定ファイルにエラーが無いか確認するコマンド）
$ haproxy -f /etc/haproxy/haproxy.cfg -c

# ログの tail 
tail -f tail -f /var/log/haproxy.log
```


#4.設定

この手順では、以下のような構成を考えます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/1dcd8c5a-6063-244d-f54e-bd1441025a08.png)
`haproxy.example.localdomain` が受けた HTTP/HTTPS リクエストを背後の2つの nginx サーバーにラウンドロビンで渡す構成です。

リクエストをForwardする先の`nginx`のインストール・構成については、この手順では触れません。

##4.1.haproxy.cfgの設定

設定として編集するのは、`/etdc/haproxy/haproxy.cfg` のみです。

デフォルト（サンプル）の設定で有効になっている 5001～5004 のポートなどは、`RHEL` 上では `SELinux` が `HAProxy` にアクセスを許可しておらずエラーになるので関連ヶ所はコメントアウトが必要です。

```/etc/haproxy/haproxy.cfg
#---------------------------------------------------------------------
# Example configuration for a possible web application.  See the
# full configuration options online.
#
#   https://www.haproxy.org/download/1.8/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    # to have these messages end up in /var/log/haproxy.log you will
    # need to:
    #
    # 1) configure syslog to accept network log events.  This is done
    #    by adding the '-r' option to the SYSLOGD_OPTIONS in
    #    /etc/sysconfig/syslog
    #
    # 2) configure local2 events to go to the /var/log/haproxy.log
    #   file. A line like the following can be added to
    #   /etc/sysconfig/syslog
    #
    #    localdomain2.*                       /var/log/haproxy.log
    #
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

    # utilize system-wide crypto-policies
    ssl-default-bind-ciphers PROFILE=SYSTEM
    ssl-default-server-ciphers PROFILE=SYSTEM

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
 defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

#---------------------------------------------------------------------
# main frontend which proxys to the backends
#---------------------------------------------------------------------
# 以下はコメントアウト
# frontend main
#    bind *:5000
#    acl url_static       path_beg       -i /static /images /javascript /stylesheets
#    acl url_static       path_end       -i .jpg .gif .png .css .js

#    use_backend static          if url_static
#    default_backend             app

 frontend http_80
    default_backend http_80
    mode http
    bind *:80

 frontend http_443
    default_backend http_443
    mode http
    bind *:443

#---------------------------------------------------------------------
# static backend for serving up images, stylesheets and such
#---------------------------------------------------------------------
# backend static
#    balance     roundrobin
#    server      static 127.0.0.1:4331 check

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
#　以下はコメントアウト (SELinux に怒られる)
#  backend app
#    balance     roundrobin
#    server  app1 127.0.0.1:5001 check
#    server  app2 127.0.0.1:5002 check
#    server  app3 127.0.0.1:5003 check
#    server  app4 127.0.0.1:5004 check

backend http_80
    mode http
    balance   roundrobin
    server nginx1 nginx1.example.localdomain:80 check
    server nginx2 nginx2.example.localdomain:80 check

backend http_443
    mode http
    balance   roundrobin
    server nginx1 nginx1.example.localdomain:443 check
    server nginx2 nginx2.example.localdomain:443 check
```

上記の config 模式図化したのが以下の図になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/2c3dd667-1550-9e70-c8e5-a67e328e9d41.png)

##4.2.書式が合っているか確認

設定ファイル(`/etc/haproxy/haproxy.cfg`) が長いので以下のコマンドで構文間違いを確認できます。

```
# haproxy.cfg の書式の確認（エラーがあった場合）
$ haproxy -f /etc/haproxy/haproxy.cfg -c
[WARNING] 226/115610 (34205) : parsing [/etc/haproxy/haproxy.cfg:124] : backend 'worker_http', another server named 'worker1' was defined without an explicit ID at line 123, this is not recommended.
[WARNING] 226/115610 (34205) : parsing [/etc/haproxy/haproxy.cfg:129] : backend 'worker_https', another server named 'worker1' was defined without an explicit ID at line 128, this is not recommended.
Configuration file is valid
$

＃確認が上手く行ったケース
$ haproxy -f /etc/haproxy/haproxy.cfg -c
Configuration file is valid
$ 
```

問題が無い場合は、`Configuration file is valid`と表示されるはずです。

#5.HTTP/HTTPS用の Firewall の設定

`RHEL`では標準で `firewalld`が有効になっているので、ロードバランシングするサービスに応じて穴開けをしてあげる必要があります。

## 5.1.現在の設定の確認

現在の設定を確認します。

```
$ firewall-cmd --get-active-zones
libvirt
  interfaces: virbr0
public
  interfaces: ens192
$
```

`interfaces: ens192`は、`public` ゾーンに存在しています。
とりあえず、設定が必要なのは「`public`」のゾーンであると記憶します。
次は「`public`」に設定されている「サービス」を確認します。

```
$ firewall-cmd --list-services --zone=public
cockpit dhcpv6-client ssh
$
```


##5.2.http / https の穴を開ける

`http (80)` と `https (443)`については、デフォルトで「サービス」の事前定義が存在しているので、それを追加するだけで `firewalld`に穴を開ける事ができます。

```
$ firewall-cmd --add-service=https --zone=public  --permanent
success
$ firewall-cmd --add-service=http --zone=public  --permanent
success
```

firewalld の設定のリロードします。(--permanentで追加した場合は、reload しないと設定が反映されない）

```
$ firewall-cmd --reload
```

設定が反映されたか確認します。

```
$ firewall-cmd --list-services --zone=public
cockpit dhcpv6-client http https ssh
```

`http``https` が追加されています。

ここでは、その他のデフォルトで穴が開いているサービスは特に変更していませんが、必要に応じてハードニングしましょう。

不必要な「サービス」は以下で削除できます。

```
firewall-cmd --remove-service=<サービス名> --zone=public --permanent
```

#6.Haproxy の起動と動作の確認

##6.1 Haproxyの起動

Haproxy を起動させます。

```
#  haproxy を起動
$ systemctl start haproxy
```

自動起動を有効化します。

```
$ systemctl enable haproxy
$ systemctl is-enabled haproxy
enabled       
```

##6.2 動作の確認

`http(s)://haproxy.example.localdomain` にアクセスしてみます。

この例では、トラフィックを割り振るバックエンドの nginxの画面をわかりやすいように変更していますが、リロードする度に接続先が切り替わるはずです。これは、`haproxy.cfg` に `roundrobin`を設定しているせいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/99425/048ee9e1-6b9b-613b-54d3-8a32769ec228.png)


#7.Security の設定をとりあえず回避して HA Proxy の動きだけ見たい場合

`HA Proxy`の設定は、複雑になってくると、まずは`RHEL`のセキュリティ設定をOFFにして`haproxy.cfg`の設定を確認したい時がどうしても出てきます。

以下は、RHELの標準のセキュリティ設定(`SELinux` と `Firewalld`) を停止させる方法です。

##7.1.SELinux を停止

SELinux を一時的に停止する 

```
$ setenforce 0　　　#　反対に稼働させるには 1 を指定
```

SELinux を恒久的に停止する

```
$ vim /etc/selinuxconig
# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=enforcing　　# ここを　disabled に
・・・
・・
・
```

##7.2.Firewalld の停止

今動いているものを一時的に止める。

```
$ systemctl stop firewalld
```

システム起動時にも上がってこないようにする

```
$ systemctl disable firewalld
```

#8.一般的なfirewalldの穴あけ方法

`HA Proxy` は L4 (TCP)レベルのロードバランサーなので、`HTTP/HTTPS` 以外のプロトコルのロードバランスも行う事ができます。

##8.1.事前定義された「サービス」の穴開けを行う
前述の通り `http (80)` と `https (443)`については、デフォルトで「サービス」の定義が存在しているので、その定義を使用して比較的簡単に firewalld に穴を開ける事ができます。


デフォルトで事前定義されている「サービス」は、`firewall-cmd  --get-services`で確認できます。

```
$ firewall-cmd  --get-services
RH-Satellite-6 amanda-client amanda-k5-client amqp amqps apcupsd audit bacula bacula-client bb bgp bitcoin bitcoin-rpc bitcoin-testnet bitcoin-testnet-rpc bittorrent-lsd ceph ceph-mon cfengine cockpit condor-collector ctdb dhcp dhcpv6 dhcpv6-client distcc dns dns-over-tls docker-registry docker-swarm dropbox-lansync elasticsearch etcd-client etcd-server finger freeipa-4 freeipa-ldap freeipa-ldaps freeipa-replication freeipa-trust ftp ganglia-client ganglia-master git grafana gre high-availability http https imap imaps ipp ipp-client ipsec irc ircs iscsi-target isns jenkins kadmin kdeconnect kerberos kibana klogin kpasswd kprop kshell kube-apiserver ldap ldaps libvirt libvirt-tls lightning-network llmnr machine-config managesieve matrix mdns memcache minidlna mongodb mosh mountd mqtt mqtt-tls ms-wbt mssql murmur mysql nfs nfs3 nmea-0183 nrpe ntp nut openvpn ovirt-imageio ovirt-storageconsole ovirt-vmconsole plex pmcd pmproxy pmwebapi pmwebapis pop3 pop3s postgresql privoxy prometheus proxy-dhcp ptp pulseaudio puppetmaster quassel radius rdp redis redis-sentinel rpc-bind rsh rsyncd rtsp salt-master samba samba-client samba-dc sane sip sips slp smtp smtp-submission smtps snmp snmptrap spideroak-lansync spotify-sync squid ssdp ssh steam-streaming svdrp svn syncthing syncthing-gui synergy syslog syslog-tls telnet tentacle tftp tftp-client tile38 tinc tor-socks transmission-client upnp-client vdsm vnc-server wbem-http wbem-https wsman wsmans xdmcp xmpp-bosh xmpp-client xmpp-local xmpp-server zabbix-agent zabbix-server
$
```

ただこれらの名前を見ても一体どういう定義なのかわかりません。

これらのデフォルトで事前定義されている「サービス」の実際の設定ファイルは、`/usr/lib/firewalld/services/`の下に保管されています。

```
$ ls  /usr/lib/firewalld/services/
RH-Satellite-6.xml       freeipa-4.xml            libvirt-tls.xml           pop3.xml               ssh.xml
amanda-client.xml        freeipa-ldap.xml         libvirt.xml               pop3s.xml              steam-streaming.xml
amanda-k5-client.xml     freeipa-ldaps.xml        lightning-network.xml     postgresql.xml         svdrp.xml
amqp.xml                 freeipa-replication.xml  llmnr.xml                 privoxy.xml            svn.xml
amqps.xml                freeipa-trust.xml        managesieve.xml           prometheus.xml         syncthing-gui.xml
apcupsd.xml              ftp.xml                  matrix.xml                proxy-dhcp.xml         syncthing.xml
<省略>
```

例えば`kube-apiserver` という名前の事前定義の「サービス」があります。
このサービスは、「サービス」名と同じフィル名の`/usr/lib/firewalld/services/kube-apiserver.xml`というファイルの中で定義されています。中身は読むとなんとなく判別できるものになっています。

```
$ cat /usr/lib/firewalld/services/kube-apiserver.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <short>Kubernetes Api Server</short>
  <description>The Kubernetes API server validates and configures data for the api objects which include pods, services, replicationcontrollers, and others.</description>
  <port protocol="tcp" port="6443"/>
</service>
$
```

「サービス」の定義ファイルの中身は上記のようなものなので、自分が使いたい「サービス」が事前定義されているかは、ファイルの中身を検索する事で発見できます。例えばポート`6443`を使うサービスが事前定義されているかどうかは、以下のコマンドで検索できます。

```
$ find /usr/lib/firewalld/services -type f -print | xargs grep 6443
/usr/lib/firewalld/services/kube-apiserver.xml:  <port protocol="tcp" port="6443"/>
$
```


事前定義されている「サービス」の `firewalld` への穴開けの方法は、基本的に全て同じで、例えば`kube-apiserver`を`public`ゾーンに追加するには以下のようにコマンドを実行します。

```
# permanent (再起動後も有効）に、設定を追加
$ firewall-cmd --add-service=kube-apiserver --zone=public  --permanent
success

# 設定の再読込
$ firewall-cmd --reload
```

## 8.2.カスタムの「サービス」を作成して穴開けを行う

例として「`Machine Config`」というサービスがあり、`22623 /tcp` というポートを使用したいとします。
まずは、以下のコマンドで自分の使用しいたい`port`がデフォルトで事前定義されてないか探してみます。

```
 find /usr/lib/firewalld/services -type f -print | xargs grep <自分の使用したいサービスのポート>
```

もし存在しない場合は、自分で新しい「サービス」を定義します。

ここでは、「`machine-config`」という`22623 /tcp` を使う新しいサービスを作成してみます。

新しいサービス名の追加

```
$ firewall-cmd --permanent --new-service machine-config
success
```

「サービス」「`machine-config`」の説明を追加

```
$ firewall-cmd --permanent --service=machine-config --set-description="OpenShift machine config access"
success
```

新しい「サービス」「`machine-config`」のポート定義を追加

```
$ firewall-cmd --service=machine-config --add-port=22623/tcp --permanent 
success
```

新しい「サービス」の設定ファイルが作成された事を確認します。ユーザー定義の「サービス」は、事前定義の「サービス」のファイルのパスとは違い、`/etc/firewalld/services/`配下に作成されます。

```
$ cat /etc/firewalld/services/machine-config.xml
<?xml version="1.0" encoding="utf-8"?>
<service>
  <description>OpenShift machine config access</description>
  <port port="22623" protocol="tcp"/>
</service>
```

作成された新しい「サービス」のファイルを読み込みます。

```
$ firewall-cmd --reload
```

作成した「サービス」を `firewalld` を透過させる「サービス」として、ここでは`public`ゾーンに追加します。

```
$ firewall-cmd --add-service=machine-config --zone=public 
success
```

現在、`firewalld`を透過するように設定されている「サービス」を確認します。`machine-config`が追加されている事を確認します。

```
$ firewall-cmd --list-services
cockpit dhcpv6-client http https kube-apiserver machine-config ssh
$
```

再起動しても設定が外れないように、現在の設定を `permanent` にするコマンドを実行します。

```
$ firewall-cmd --runtime-to-permanent
success
```


#9.SELinux の設定

特種なポートを使うアプリケーションのロードバランスを行う場合、`HA Proxy`がそのポートを使用する事が許可されていない場合は、`SELinux`が`HA Proxy`の動きをブロックするため、`haproxy` が起動しません。

例えば設定を変更して、`systemctl`で再起動しようとすると、以下のように起動できないはずです。

```
$ systemctl restart haproxy.service
Job for haproxy.service failed because the control process exited with error code.
See "systemctl status haproxy.service" and "journalctl -xe" for details.
$
```


## 9.1 audit.log の確認

`SELinux`のログは`/var/log/audit/audit.log` に出力されるので、`haproxy` が動かない場合は、動きが止められてないか、`audit.log`を確認します。

しかし `audit.log` は、そのままでは見にくいので `ausearch` というコマンドで、フォーマットされた `audit.log`　を見るツールが用意されています。

`haproxy` 関連の `audit.log` のエラーは以下のコマンドで確認する事ができます。

```
[root@lb1 ~]# ausearch -c 'haproxy' --raw
type=AVC msg=audit(1602169554.880:195): avc:  denied  { name_bind } for  pid=2903 comm="haproxy" src=6443 scontext=system_u:system_r:haproxy_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
type=AVC msg=audit(1602169554.880:196): avc:  denied  { name_bind } for  pid=2903 comm="haproxy" src=22623 scontext=system_u:system_r:haproxy_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket permissive=0
[root@lb1 ~]# 
```

`denied` という単語が見えるので、何かが拒否されているのがわかります。  

## 9.2 audit2allow の使用

`ausearch` での出力された `denied` のログを `audit2allow` というコマンドにパイプで渡す事で、行うべき `SELinux`の設定を提案してくれます。ここでは、`my-haproxy`というファイル名にします。

```
$ ausearch -c 'haproxy' --raw | audit2allow -M my-haproxy
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i my-haproxy.pp

$ ls -ltr
-rw-r--r--. 1 root root  308 Oct  8 11:12 my-haproxy.te
-rw-r--r--. 1 root root  969 Oct  8 11:12 my-haproxy.pp
$
```
`.pp`と`.te` というファイルができますが、`.te`は可読なので中身を確認してみます。

```
$ cat my-haproxy.te 

module my-haproxy 1.0;

require {
        type haproxy_t;
        type unreserved_port_t;
        class tcp_socket name_bind;
}

#============= haproxy_t ==============

#!!!! This avc can be allowed using one of the these booleans:
#     nis_enabled, haproxy_connect_any
allow haproxy_t unreserved_port_t:tcp_socket { name_bind name_connect };
$
```

`allow haproxy_t unreserved_port_t:tcp_socket name_bind;` という設定をする事をする事をすすめられています。

この設定を適用するには、以下のコマンドを実行します。

```
semodule -i my-haproxy.pp
```

これで `haproxy` が起動するようになっているはずです。

# 10.rsyslog を有効化する

`HA Proxy` がきちんと動いているかどうか、確認したいなと思い、ログはどこか・・・と探した所、`rsyslog` で外に吐く設定をしてあげる必要があるようです。`/var/log/haproxy.log` 　というファイルにログを吐くようにします。

`/etc/rsyslog.conf`　を以下のように編集します。

``` /etc/rsyslog.conf
・・・
# Provides UDP syslog reception
# for parameters see http://www.rsyslog.com/doc/imudp.html
#
#module(load="imudp") # needs to be done just once
#input(type="imudp" port="514")
# 以下の2行を追加
$ModLoad imudp
$UDPServerRun 514

<省略>

# *.info;mail.none;authpriv.none;cron.none                /var/log/messages
# 以下に書き換え (local2 が /var/log/message に出力されないようにするため。 /var/log/haproxy.log に出力したい)
*.info;mail.none;authpriv.none;cron.none;local2.none                /var/log/messages

<省略>

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log

# ここも追加 /var/log/haproxy.log にログを出力する。
# Save HAProxy maessage to haproxy.log
local2.*                                                /var/log/haproxy.log
```

設定を編集したら`rsyslog`を再起動します。

```
systemctl restart rsyslog
```