どこからもアクセスできなくなっちゃった

問題文

問題文は以下の通りでした。

k8sのクラスターを、マスター1台(master)、ワーカー2台(worker01, worker02)、ロードバランサー1台(lb)という構成で構築しました。
ロードバランサーにはHAProxyを用いており、kube-apiserverであるmaster(172.16.0.1:6433)へのプロキシと、各ワーカーの30080のNodePort(172.16.0.11:30080, 172.16.0.12:30080)へのロードバランシング, k8sクラスタの各ノードのデフォルトゲートウェイとしてiptablesを用いたMasqueradeを行っています。

k8sクラスタでは、nginxをreplica 数1つで展開するDeploymentと、それを外部にNodePort 30080で公開するServiceが作成されています。
このnginxに対して外部(external)からの通信においてhost01からのみアクセスできるといったアクセス制限を行うため、NetworkPolicyを用いて制限をかけたところ、host01からもアクセスできなくなってしまいました。

なぜhost01からもアクセスできないのか原因と解決方法、解決でき再起動しても問題のない作業手順を報告してください。

image

初期状態とゴール

初期状態

  • host01からcurl -m 2 192.168.0.1:30080 を実行してタイムアウトになる
  • host02からcurl -m 2 192.168.0.1:30080 を実行してタイムアウトになる
  • k8sにNamespace, Deployment, Service, NetworkPolicyが適用されている

終了状態

  • NetworkPolicyを利用してhost01からの通信のみを許可している
  • host01からcurl -m 2 192.168.0.1:30080 を実行してk8sでデプロイしたnginxのデフォルトページを表示できる
  • host02からcurl -m 2 192.168.0.1:30080 を実行してタイムアウトになる
  • 終了状態が永続化されている(再起動しても上記の終了状態を確認することができる)

解説

原因

NetworkPolicyで192.168.0.10からの通信を許可し、それ以外の通信を拒否しているにもかかわらず、192.168.0.10からの通信が拒否されていました。
これは、LBにHAProxyを利用していたため、Podへの通信の送信元アドレスがHAProxyのものになり通信が拒否されていました。

解決方法としては、LBをHAProxyのようなリバースプロキシではなくL4 LBに変更します。
今回はL4 LBにkeepalived(lvs)を利用します。

keepalivedの設定には、k8sのmasterへの通信の設定とworkerノードへの通信の設定を行います。k8sにおいてNodePortで公開した場合いずれかのworkerノードのNodePortに対して通信を行うことで、Podがそのworkerノードに展開されていなくとも通信することができます。これは、workerノードにPodが展開されていない場合にPodが展開されている他のworkerノードにルーティングするためです。このルーティングの際に通信をSNATするため送信元IPが変更されてしまいます。そこで、keepalivedでTCP_CHECKでポートチェックを行いPodが展開されているworkerノードのみにロードバランシングするようにします。

また、keepalivedからのポートチェックの通信をNetworkPolicyで許可します。

keepalivedのインストール, 設定

まず、LBをHAProxyからkeepalived(lvs)に変更し、原因で述べた設定を行います。

# lb
$ sudo apt install keepalived ipvsadm
$ sudo vim /etc/keepalived/keepalived.conf
global_defs {
}

virtual_server 192.168.0.1 6443 {
  delay_loop 10
  lvs_sched rr
  lvs_method NAT
  protocol TCP

  real_server 172.16.0.1 6443 {
    weight 1
    inhibit_on_failure
    TCP_CHECK {
      connect_port 6443
      connect_timeout 30
      ng_get_retry 3
      delay_before_retry 3
    }
  }
}

virtual_server 192.168.0.1 30080 {
  delay_loop 10
  lvs_sched rr
  lvs_method NAT
  protocol TCP

  real_server 172.16.0.11 30080 {
    TCP_CHECK {
      connect_timeout 10
      ng_get_retry 3
      delay_before_retry 3
    }
  }

  real_server 172.16.0.12 30080 {
    TCP_CHECK {
      connect_timeout 30
      ng_get_retry 3
      delay_before_retry 3
    }
  }
}
$ sudo systemctl stop haproxy
$ sudo systemctl disable haproxy
$ sudo systemctl start keepalived
$ sudo systemctl enable keepalived

NetworkPolicyの修正

LBからのTCP_CHECKを許可するように修正しapplyします。

# host01
$ vim ~/manifests/30-networkpolicy.yaml
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: my-np
  namespace: my-ns
spec:
  podSelector:
    matchLabels:
      app: nginx
  policyTypes:
    - Ingress
  ingress:
    - from:
        - ipBlock:
            cidr: 192.168.0.10/32
        - ipBlock:
            cidr: 172.16.0.254/32
$ kubectl apply -f ~/manifests/30-networkpolicy.yaml

終了状態の確認

以下のように終了状態を確認できます。

# host01
user@host01:~$ curl -m 2 192.168.0.1:30080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>


# host02
user@host02:~$ curl -m 2 192.168.0.1:30080
curl: (28) Connection timed out after 2003 milliseconds

解説は以上です。

採点基準

  1. 適切な原因が報告出来ている(20%)
    • LBにHAProxyを利用しているため送信元IPがLBのものになることが言及されている。
  2. 適切な解決方法が報告できている(30%)
    • LBをKeepalivedのようなL4 LBに変更する (10%)
    • workerノードにPodがない場合にkube-proxyによってSNATされるためLBからヘルスチェックを行う (10%)
    • LBからのヘルスチェックのためのNetworkPolicyを書く(追記・新規どちらでも可) (10%)
  3. 報告された作業手順で解決することができる(50%)
    • host01からtimeoutすることなくcurlが実行でき、host02からは出来ない (50%)

講評

この問題は、まさしく同じ構成で構築したときにL7LBだと送信元IPアドレスが書き換わってしまってNetworkPolicyで制限をかけれない!という状況に陥りL4LB(keepalived)に変更することで対応した経験から作成しました。
解説にもある通り、LBで送信元IPアドレスを書き換えていなくてもPodが存在しないノードにロードバランシングされた場合はk8sのServiceによってPODに対してSNATされてしまうため、ヘルスチェックを行う必要があるというのがハマりポイントかと思います。

解答について

今回想定していた解法としては、「LBをHAProxyからL4LB(keepalived)に変更する」というものでした。しかし、一部の解答でHAProxyを透過モードで動作させることで送信元IPアドレスを書き換えずにロードバランシングを行うという解答を提出して頂きました!。HAProxyでそのような設定ができるとは知らなかったのでとても勉強になりました。

終わりに

問題を解いていただきありがとうございました!