Dockerコンテナの、Keepalived + LVS冗長化　（1 / 2　構築編）

###概要

サーバたるもの、仮想マシンであろうがコンテナであろうが、冗長性を兼ね備えていなければなりません。

そこで、Dockerを利用したサービス用サーバについても、別筐体間で冗長化・負荷分散を試みました。


<br>Dockerのネイティブな機構でも、High Availability（以下、HA）が（ある|サポートされる）のかも知れませんが、今回はコンテナのホストサーバ間で技術的にも枯れたKeepalived + LVSを用いて、コンテナへのアクセスを冗長化・負荷分散する構成を取りました。

構成の概要は以下の図の通りとなります。

<br>
![ka_lvs_docker.png](https://qiita-image-store.s3.amazonaws.com/0/42848/e2371c43-4233-d694-12b5-3c77559a83d6.png)



コンテナ・ホストのInbondインタフェイスと、DockerコンテナであるWebサーバweb-01. web-02のゲートウェイとして、VRRP（Keepalived）によるVIPを設定しました。

図中で左のCentOS7がMasterで、InbondインタフェイスもしくはDocker向けゲートウェイのいずれかがDownした場合は、右のBackup CentOS7にフェイルオーバします。


<br>また、各CentOS内で稼働するWebサーバをLVS（IP-LVS）によって負荷分散冗長化しており、HTTPヘルスモニタリングで死活監視をし、Webサーバプロセス（Nginx）がDownしたコンテナに対して、リクエストを回さないように設定しました。 　

この構成の結果、ホストCentOSへのHTTPリクエスト（TCP 80）はVIP 192.168.1.220に到達した時点でVRRP MasterがNATし、web-01 or web-02のIPアドレスに宛先NATの後にL3転送されます。


<br>
###Keepalivedの準備

Keepalivedの設定ファイル /etc/keepalived/keepalived.conf を掲載します。
（※予め、ipvsdam, keepalivedをインストールし、ip_vs.koをmodprobeしている前提）



```conf:/etc/keepalived/keepalived.conf　＠Master**
! Configuration File for keepalived

global_defs {
   router_id LVS_DOCKER
}

vrrp_instance WEB-HA-01 {
    state MASTER
    interface eth0
    virtual_router_id 100
    priority 100
    advert_int 3
    authentication {
        auth_type AH
        auth_pass docker-web
    }
    virtual_ipaddress {
        192.168.1.220 dev eth0
        192.168.100.254 dev ha-01
    }
}

virtual_server 192.168.1.220 80 {
    delay_loop 5
    lb_algo rr
    lb_kind NAT
    protocol TCP

    real_server 192.168.100.221 80 {
        weight 1
        HTTP_GET {
            url {
              path /ka
	      status_code 200
            }
            connect_timeout 5
            nb_get_retry 3
            delay_before_retry 3
        }
    }

    real_server 192.168.100.222 80 {
        weight 1
        HTTP_GET {
            url {
              path /ka
	      status_code 200
            }
            connect_timeout 5
            nb_get_retry 3
            delay_before_retry 3
        }
    }

}
```

<br>

```conf:/etc/keepalived/keepalived.conf　＠Backup
! Configuration File for keepalived

global_defs {
   router_id LVS_DOCKER
}

vrrp_instance WEB-HA-01 {
    state BACKUP
    interface eth0
    virtual_router_id 100
    priority 90
    advert_int 3
    authentication {
        auth_type AH
        auth_pass docker-web
    }
    virtual_ipaddress {
        192.168.1.220 dev eth0
        192.168.100.254 dev ha-01
    }
}

virtual_server 192.168.1.220 80 {
    delay_loop 5
    lb_algo rr
    lb_kind NAT
    protocol TCP

    real_server 192.168.100.221 80 {
        weight 1
        HTTP_GET {
            url {
              path /ka
	      status_code 200
            }
            connect_timeout 5
            nb_get_retry 3
            delay_before_retry 3
        }
    }

    real_server 192.168.100.222 80 {
        weight 1
        HTTP_GET {
            url {
              path /ka
	      status_code 200
            }
            connect_timeout 5
            nb_get_retry 3
            delay_before_retry 3
        }
    }

}
```
<br>
###Dockerの準備

<br>続いて、Webサービスを提供するDockerコンテナのイメージを作成します。

CentOS7の正系・待機系で、同じimageを用意するため、以下のDockerfileを作成してください。

<br>

```conf:Dokerfile
#
# Nginx Dockerfile
#

FROM centos:centos7

ADD ./init.sh /usr/local/bin/init.sh

RUN yum -y update \
 && yum -y install epel-release \
 && yum -y install nginx \
 && chmod +x /usr/local/bin/init.sh

# Define default command.
CMD ["/usr/local/bin/init.sh"]

```

<br>このファイルと同じディレクトリ内に、CMDで実行指定しているinit.shを作成します。


```sh:init.sh
#!/bin/sh

/usr/sbin/nginx && /bin/bash

```

<br>ここまできたら、この2つのファイルがあるディレクトリにて、

```conf
docker build -t TAGNAME .
```
（※TAGNAMEは任意のimage tag名称）

上のコマンドで、CentOS7のbase imageからnginxをインストール済み＆事後に`docker attach`可能なイメージを構築します。

こうして作成したWebサーバ用コンテナを、次のように起動させます。

```conf
docker run --name web-01  -ti  myimg/nginx-c7:v4
```

<br>
###HA用ネットワークの準備

Linuxコンテナ向けのネットワークブリッジ・ユーティリティであるpipeworkを用いて、コンテナにHAネットワーク側のNICを増設します。

（pipework : https://github.com/jpetazzo/pipework）


```text:＠Master
[root@cent7-01 ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED              STATUS              PORTS               NAMES
9f0e6ede85ca        myimg/nginx-c7:v4   /usr/local/bin/init.   About a minute ago   Up About a minute                       web-01              

[root@cent7-01 ~]# pipework ha-01 9f0e6ede85ca 192.168.100.221/24
```

（※"ha-01"は任意のブリッジ名）

<br>

```text:＠Backup
[root@cent7-02 nginx-c7]# docker ps
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
937e2a2038e2        myimg/nginx-c7:v4   /usr/local/bin/init.   9 seconds ago       Up 8 seconds                            web-02              

[root@cent7-02 nginx-c7]# pipework ha-01 937e2a2038e2 192.168.100.222/24
```

<br>次いで、コンテナWebサーバを外部ネットワークからのHTTPリクエストに応答するために、Routing情報を変更します。

Dockerは新規コンテナ起動時に、デフォルトでは172.17.0.0/16内の空きIPをアサインし、コンテナホストに作成済みのブリッジ docker0のIP 172.17.42.1をデフォルトルートとして設定します。

ただ、このデフォルトルート情報は`docker attach`したbashセッションからは変更が禁止されているようです。

<br>そこで、Dockerコンテナに割り当てられた Linux Namespaceを操作可能にし、そのNamespaceを指定した`ip netns exec`コマンドによって、コンテナ内のルーティングを変更する事にしました。

（※Linux NamespaceはDockerがホストのネットワークとコンテナ（ゲスト）のネットワークを隔離するために、内部的に利用されています。）

手順としては、

- Dockerコンテナの実行プロセスPIDを調べる
- 上で調べたPIDが属するNamespaceを`ip netns`コマンドで操作可能とするため、/var/run/netns/ディレクトリにリンクを作成する

といった要領です。


```text:＠Master
[root@cent7-01 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS              PORTS               NAMES
9f0e6ede85ca        myimg/nginx-c7:v4   /usr/local/bin/init.   15 minutes ago      Up 15 minutes                           web-01              

[root@cent7-01 ~]# docker inspect web-01 | grep -i pid
        "Pid": 17917,

[root@cent7-01 ~]# ln -s /proc/17917/ns/net /var/run/netns/web-01
[root@cent7-01 ~]# ip netns exec web-01 ip route sh 
default via 172.17.42.1 dev eth0 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.28 
192.168.100.0/24 dev eth1  proto kernel  scope link  src 192.168.100.221 

[root@cent7-01 ~]# ip netns exec web-01 ip route del default
[root@cent7-01 ~]# ip netns exec web-01 ip route add default via 192.168.100.254

[root@cent7-01 ~]# ip netns exec web-01 ip route sh 
default via 192.168.100.254 dev eth1 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.28 
192.168.100.0/24 dev eth1  proto kernel  scope link  src 192.168.100.221 
```

<br>

```text:＠Backup
[root@cent7-02 ~]# docker inspect web-02 | grep -i pid
        "Pid": 14886,

[root@cent7-02 ~]# ln -s /proc/14886/ns/net /var/run/netns/web-02

[root@cent7-02 ~]# ip netns exec web-02 ip route sh
default via 172.17.42.1 dev eth0 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.17 
192.168.100.0/24 dev eth1  proto kernel  scope link  src 192.168.100.222 

[root@cent7-02 ~]# ip netns exec web-02 ip route del default
[root@cent7-02 ~]# ip netns exec web-02 ip route add default via 192.168.100.254

[root@cent7-02 ~]# ip netns exec web-02 ip route sh
default via 192.168.100.254 dev eth1 
172.17.0.0/16 dev eth0  proto kernel  scope link  src 172.17.0.17 
192.168.100.0/24 dev eth1  proto kernel  scope link  src 192.168.100.222 
```

<br>以上の設定・手順を終えた時点で、

- 2台のホストに跨るコンテナ2台間で、LVSの形成（Round-Robin負荷分散）
- サービスを受け付けるホストの eth0側で、VIP 192.168.1.220が稼働
- 実際のWebサーバとなるコンテナを繋いだ内部ネットワーク ha-01側で、VIP 192.168.100.254が稼働

これらの環境が出来上がりました。

<br>次の投稿では、上述のLVS・Keepalivedの状態確認の方法について述べたいと思います。


**To be continued !**