remoteノードでbundle実行

Pacemakerのクラスタからremoteリソースノード(remoteノード)上に指示を出して、bundleリソースを管理、実行することが可能です。
この機能は、Pacemaker1.1.17のBundleリソースのリリースから機能的には含まれていたようですが、確認したことがありませんでした。

以下の図に示すようにPacemaker-remoteで接続したremoteノード上でbundleリソースを起動します。
dockerサービスを起動するのは、Pacemakerのクラスタノードではなく、remoteノードとなります。

![RemoteBundle.png](https://qiita-image-store.s3.amazonaws.com/0/80018/f68deefd-ccf6-6650-97e9-922d783832aa.png)


本来、bundleで利用するイメージは、イメージサーバで管理するのですが、今回は、remoteノードにそれぞれ同一のものを用意しておきます。

機能的にはPM1.1.17から入っているようですが、動作確認にはPacemaker2.0系のmaster(efbf81b659)を利用しました。

#環境
今回は、CentOS7で全てVMで作っています。
 - Pacemakerノード 2台(cent7-host1,cent7-host2)
 - remoteノード 2台(cent7-remote1,cent7-remote2)

#準備
Pacemakerノードには、corosyncやPacemakerなど、Pacemakerノードに必要なパッケージをインストールしておき、corosync/Pacemakerでクラスタが構成出来るようにします。
remoteノードには、Pacemaker-remoteや、resource-agentなどremote-nodeとして必要なパッケージををインストールして、dockerサービス、paceaker-remoteサービスを起動しておきます。
さらに、remoteノードには、dockerもインストールし、関連リンクにあるように、Pacemaker-remoteがインストールしてある起動するコンテナのpcmktest:http(apacheのコンテナ)イメージを作成しておきます。
関連リンクの記述も参考に準備を行ってください。

#設定
crmシェルがbundleに対応していない為、remoteノード設定とbundle設定は別個で設定を流すことにします。
なお、remoteノードがbundleよりも先に起動している必要が有るため、一緒の設定で流すには、remoteノードが起動してからbundleリソースが起動するような制約が必要となります。

#### /etc/hosts
それぞれのPacemakerノードで、crm設定内のremoteリソースのserver値を解決させる為に、remoteノードを記述します。

```
(snip)
192.168.201.174 cent7-remote1 remote1
192.168.201.175 cent7-remote2 remote2
(snip)
```

#### crmファイルの設定とcibファイルの編集

```
property no-quorum-policy="ignore" \
        stonith-enabled="false" \
        startup-fencing="false"

rsc_defaults resource-stickiness="INFINITY" \
        migration-threshold="1"

primitive remote1 ocf:pacemaker:remote \
  params \
        server="cent7-remote1"

primitive remote2 ocf:pacemaker:remote \
  params \
        server="cent7-remote2"

```

一旦、上記をcrmシェルで流し込んだ後で、/var/lib/pacemaker/cibをコピーして、以下の内容を追加しておきます。
今回は、各remoteノードにhttpd-bundleのコンテナを１つずつ配置します。
(この作成したcibファイルを元にcibadmin -Mコマンドで流し込みますが、一度、cibファイルが作成出来れば、最初のcrmファイルでのremote設定の流し込みは不要です。)

Pacemakerノード内のbundleの場合は、network設定にip-range-startなどを設定しますが、remoteノードのbundleの場合は、代わりにcontrol-portをremoteノード側のpacemaker_remotedのデフォルトポート(3121)とズラして設定します。
この設定があると、Pacemakerノードは、bundleがremoteノードでの実行だと理解する仕組みになっているようです。
![RemoteBundle2.png](https://qiita-image-store.s3.amazonaws.com/0/80018/03997e60-fcf7-13db-37ac-643a40e35366.png)


```
    <resources>
★resourceセクション
(snip)
      <bundle id="httpd-bundle">
        <docker image="pcmktest:http" replicas="2" options="--log-driver=journald"/>
        <network control-port="3123">
          <port-mapping id="httpd-port" port="80"/>
        </network>
        <storage>
          <storage-mapping id="httpd-root" source-dir-root="/var/local/containers" target-dir="/var/www/html" options="rw"/>
          <storage-mapping id="httpd-logs" source-dir-root="/var/log/pacemaker/bundles" target-dir="/etc/httpd/logs" options="rw"/>
        </storage>
        <primitive class="ocf" id="httpd" provider="heartbeat" type="apache">
          <operations>
            <op name="start" interval="0s" timeout="60s" on-fail="restart" id="httpd-start-0s"/>
            <op name="monitor" interval="10s" timeout="60s" on-fail="restart" id="httpd-monitor-10s"/>
            <op name="stop" interval="0s" timeout="60s" on-fail="block" id="httpd-stop-0s"/>
          </operations>
        </primitive>
      </bundle>
(snip)
    </resources>
★constraintsセクション
    <constraints>
      <rsc_location id="rsc_location-1" rsc="httpd-bundle" resource-discovery="exclusive">
        <rule id="rsc1_location-rule-1" score="10">
          <expression attribute="#uname" operation="eq" value="remote1" id="rsc1_location-rule-1-expression"/>
        </rule>
        <rule id="rsc1_location-rule-2" score="20">
          <expression attribute="#uname" operation="eq" value="remote2" id="rsc1_location-rule-2-expression"/>
        </rule>
      </rsc_location>
    </constraints>
(snip)
```
#### 設定ファイルの流し込み
cibadmin -Mコマンド(cibadmin -M -x 編集したcib.xmlファイル名)を、Pacemakerノードで実行してbundle設定を流し込みます。

なお、remoteノードでのbundleの実行の制約として、remoteノードで実行されるコンテナはbundle毎に１つのみとなります。(replica-per-nodeは１に制限される)

#動作
###起動後

```
[root@cent7-host1 ~]# crm_mon -1 -Af
Stack: corosync
Current DC: cent7-host1 (version 2.0.0-efbf81b659) - partition with quorum
Last updated: Wed Dec  5 14:04:25 2018
Last change: Wed Dec  5 13:58:03 2018 by root via cibadmin on cent7-host1

6 nodes configured
8 resources configured

Online: [ cent7-host1 cent7-host2 ]
RemoteOnline: [ remote1 remote2 ]
GuestOnline: [ httpd-bundle-0@cent7-host2 httpd-bundle-1@cent7-host1 ]

Active resources:

 remote1        (ocf::pacemaker:remote):        Started cent7-host1
 remote2        (ocf::pacemaker:remote):        Started cent7-host2
 Docker container set: httpd-bundle [pcmktest:http]
   httpd-bundle-0       (ocf::heartbeat:apache):        Started remote2
   httpd-bundle-1       (ocf::heartbeat:apache):        Started remote1

Node Attributes:
* Node cent7-host1:
* Node cent7-host2:
* Node httpd-bundle-0@cent7-host2:
* Node httpd-bundle-1@cent7-host1:
* Node remote1:
* Node remote2:

Migration Summary:
* Node cent7-host2:
* Node cent7-host1:
* Node remote1:
* Node remote2:
* Node httpd-bundle-0@cent7-host2:
* Node httpd-bundle-1@cent7-host1:
```

###コンテナの状態
各remote接続ノードで、コンテナが起動していることが確認出来ます。

```
[root@cent7-remote1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
67053ace5334        pcmktest:http       "/usr/sbin/pacemak..."   7 minutes ago       Up 7 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:3123->3123/tcp   httpd-bundle-docker-1

[root@cent7-remote2 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
777fcf43081c        pcmktest:http       "/usr/sbin/pacemak..."   7 minutes ago       Up 7 minutes        0.0.0.0:80->80/tcp, 0.0.0.0:3123->3123/tcp   httpd-bundle-docker-0
```

###コンテナサービス(HTML)へのアクセスの確認
各remoteノードのアドレスにwgetするとコンテナ内のapacheからindex.htmlが取得出来ます。

```
[root@cent7-host1 ~]# wget http://192.168.28.174/
--2018-12-05 14:12:11--  http://192.168.28.174/
192.168.28.174:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 110 [text/html]
`index.html.1' に保存中

100%[=============================================================================================================================================================================>] 110         --.-K/s 時間 0s      

2018-12-05 14:12:11 (8.00 MB/s) - `index.html.1' へ保存完了 [110/110]

[root@cent7-host1 ~]# cat index.html.1 
<html>
<head><title>Bundle test</title></head>
<body>
<h1>httpd-bundle-1 @ cent7-remote1</h1>
</body>
</html>

[root@cent7-host1 ~]# wget http://192.168.28.175/
--2018-12-05 14:11:48--  http://192.168.28.175/
192.168.28.175:80 に接続しています... 接続しました。
HTTP による接続要求を送信しました、応答を待っています... 200 OK
長さ: 110 [text/html]
`index.html' に保存中

100%[=============================================================================================================================================================================>] 110         --.-K/s 時間 0s      

2018-12-05 14:11:48 (7.97 MB/s) - `index.html' へ保存完了 [110/110]

[root@cent7-host1 ~]# cat index.html 
<html>
<head><title>Bundle test</title></head>
<body>
<h1>httpd-bundle-0 @ cent7-remote2</h1>
</body>
</html>

```

### ホスト片系停止
remoteノード(remoteリソース)が残ったホスト側へ収容・配置されますが、remoteノード上のコンテナなどは停止しません。

```
[root@cent7-host2 ~]# crm_mon -1 -Af
Stack: corosync
Current DC: cent7-host2 (version 2.0.0-efbf81b659) - partition WITHOUT quorum
Last updated: Wed Dec  5 14:13:47 2018
Last change: Wed Dec  5 13:58:02 2018 by root via cibadmin on cent7-host1

6 nodes configured
8 resources configured

Online: [ cent7-host2 ]
OFFLINE: [ cent7-host1 ]
RemoteOnline: [ remote1 remote2 ]
GuestOnline: [ httpd-bundle-0@cent7-host2 httpd-bundle-1@cent7-host2 ]

Active resources:

 remote1        (ocf::pacemaker:remote):        Started cent7-host2
 remote2        (ocf::pacemaker:remote):        Started cent7-host2
 Docker container set: httpd-bundle [pcmktest:http]
   httpd-bundle-0       (ocf::heartbeat:apache):        Started remote2
   httpd-bundle-1       (ocf::heartbeat:apache):        Started remote1

Node Attributes:
* Node cent7-host2:
* Node httpd-bundle-0@cent7-host2:
* Node httpd-bundle-1@cent7-host2:
* Node remote1:
* Node remote2:

Migration Summary:
* Node cent7-host2:
* Node remote1:
* Node remote2:
* Node httpd-bundle-0@cent7-host2:
* Node httpd-bundle-1@cent7-host2:

[root@cent7-remote1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
67053ace5334        pcmktest:http       "/usr/sbin/pacemak..."   16 minutes ago      Up 16 minutes       0.0.0.0:80->80/tcp, 0.0.0.0:3123->3123/tcp   httpd-bundle-docker-1
[root@cent7-remote2 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                        NAMES
777fcf43081c        pcmktest:http       "/usr/sbin/pacemak..."   16 minutes ago      Up 16 minutes       0.0.0.0:80->80/tcp, 0.0.0.0:3123->3123/tcp   httpd-bundle-docker-0
```

###全停止
残ったホストも停止します。remoteノードでのコンテナの実行も停止します。

```
[root@cent7-remote1 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

[root@cent7-remote2 ~]# docker ps 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
```


#補足
bundle構成に限りませんが、pacemaker_remoteを使ったクラスタ構成の場合に利用されるremoteリソースですが、実はstart/stopなどのオペレーションは存在しません。
remoteリソースは、あくまでもpacemaker_remotedが起動している先をノードとして認識し、通信する為の疑似的なリソースになります。
実際にpengineでは、CIBから状態遷移計算時に、内部的にremoteリソースからノードを作り出してremoteノードとして処理を行っています。
また、pacemakerノードのcrmdは、remoteリソースで認識されたノードのpacemaker_remotedと通信を行っています。
よって、remoteのbundle実行の場合も、remoteノード側のbundle(コンテナ)内のpacemaker_remotedとの通信を実行・監視しているのも、pacemakerノードのcrmdです。

```
@status.c
(snip)
gboolean
cluster_status(pe_working_set_t * data_set)
{
    xmlNode *config = get_xpath_object("//"XML_CIB_TAG_CRMCONFIG, data_set->input, LOG_TRACE);
(snip)
    unpack_nodes(cib_nodes, data_set);

    if(is_not_set(data_set->flags, pe_flag_quick_location)) {
        unpack_remote_nodes(cib_resources, data_set);
    }

    unpack_resources(cib_resources, data_set);
    unpack_tags(cib_tags, data_set);

    if(is_not_set(data_set->flags, pe_flag_quick_location)) {
        unpack_status(cib_status, data_set);
    }

    set_bit(data_set->flags, pe_flag_have_status);
    return TRUE;
}
(snip)
@unpack.c
(snip)
gboolean
unpack_remote_nodes(xmlNode * xml_resources, pe_working_set_t * data_set)
{
    xmlNode *xml_obj = NULL;

    /* generate remote nodes from resource config before unpacking resources */
    for (xml_obj = __xml_first_child(xml_resources); xml_obj != NULL; xml_obj = __xml_next_element(xml_obj)) {
        const char *new_node_id = NULL;

        /* first check if this is a bare metal remote node. Bare metal remote nodes
         * are defined as a resource primitive only. */
        if (xml_contains_remote_node(xml_obj)) {
            new_node_id = ID(xml_obj);
            /* The "pe_find_node" check is here to make sure we don't iterate over
             * an expanded node that has already been added to the node list. */
            if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                crm_trace("Found baremetal remote node %s in container resource %s", new_node_id, ID(xml_obj));
                pe_create_node(new_node_id, new_node_id, "remote", NULL,
                               data_set);
            }
            continue;
        }

        /* Now check for guest remote nodes.
         * guest remote nodes are defined within a resource primitive.
         * Example1: a vm resource might be configured as a remote node.
         * Example2: a vm resource might be configured within a group to be a remote node.
         * Note: right now we only support guest remote nodes in as a standalone primitive
         * or a primitive within a group. No cloned primitives can be a guest remote node
         * right now */
        if (crm_str_eq((const char *)xml_obj->name, XML_CIB_TAG_RESOURCE, TRUE)) {
            /* expands a metadata defined remote resource into the xml config
             * as an actual rsc primitive to be unpacked later. */
            new_node_id = expand_remote_rsc_meta(xml_obj, xml_resources, data_set);

            if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                crm_trace("Found guest remote node %s in container resource %s", new_node_id, ID(xml_obj));
                pe_create_node(new_node_id, new_node_id, "remote", NULL,
                               data_set);
            }
            continue;

        } else if (crm_str_eq((const char *)xml_obj->name, XML_CIB_TAG_GROUP, TRUE)) {
            xmlNode *xml_obj2 = NULL;
            /* search through a group to see if any of the primitive contain a remote node. */
            for (xml_obj2 = __xml_first_child(xml_obj); xml_obj2 != NULL; xml_obj2 = __xml_next_element(xml_obj2)) {

                new_node_id = expand_remote_rsc_meta(xml_obj2, xml_resources, data_set);

                if (new_node_id && pe_find_node(data_set->nodes, new_node_id) == NULL) {
                    crm_trace("Found guest remote node %s in container resource %s which is in group %s", new_node_id, ID(xml_obj2), ID(xml_obj));
                    pe_create_node(new_node_id, new_node_id, "remote", NULL,
                                   data_set);
                }
            }
        }
    }
    return TRUE;
}
```

#関連リンク
 - https://qiita.com/HideoYamauchi/items/7527eba39f4331fdc9df