#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.設定
この手順では、以下のような構成を考えます。
haproxy.example.localdomain
が受けた HTTP/HTTPS リクエストを背後の2つの nginx サーバーにラウンドロビンで渡す構成です。
リクエストをForwardする先のnginx
のインストール・構成については、この手順では触れません。
##4.1.haproxy.cfgの設定
設定として編集するのは、/etdc/haproxy/haproxy.cfg
のみです。
デフォルト(サンプル)の設定で有効になっている 5001~5004 のポートなどは、RHEL
上では SELinux
が HAProxy
にアクセスを許可しておらずエラーになるので関連ヶ所はコメントアウトが必要です。
#---------------------------------------------------------------------
# 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
##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
を設定しているせいです。
#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
を以下のように編集します。
・・・
# 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