ha 45.5 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
++ Введение

В данной статье будет рассмотрен способ организации Linux HA кластера на базе широкопотребного аппаратного обеспечения. Для удобства дальнейшей эффективной эксплуатации и консолидации будет предложено использовать LXC.

Для большинства задач необходимо корректно реплицировать два типа данных:
** БД
** Файлы

Однако, как показала практика, для синхронизации конфигурационных файлов хост-узлов удобно использовать отдельный инструмент от синхронизации всех остальных файлов. Итого в рамках данной статьи предлагается использовать следующий набор ПО:
** lxc+ipw для изоляции окружений
** percona+galera для репликации БД
** clsync+rsync для репликации файлов
** csync2 для репликации конфигураций

++ HOWTO

++ Аппаратное обеспечение

Допустим, у вас в наличии имеется 3 сервера. 2 из них будем использовать для HA-кластера, а 3-ий для системы резервного копирования.

Итого получается примерно следующая схема:

    srv-0 <----> srv-1
      |            |
      +-> backup <-+

В рамках данного примера назовём:
srv-0 — anubis
srv-1 — seth
   
++ Установка хост-систем

В качестве ОС для хост-систем сгодится любая популярная ОС на базе современных ядер Linux и GNU окружения. Однако в моём случае, данная схема была опробована только на Debian и Gentoo.

Не рекомендую использовать LVM, ибо ничего кроме overhead-ов в данной ситуации вы от этого не получите, IMHO. В качестве ФС, нами были опробованы BtrFS, XFS, Ceph и ext4, и лучше всего прижился ext4, хоть и в нём не хватает возможности устанавливать квоты по директориям, а не по uid-ам. BtrFS был плох тем, что при любой отладке связанной как-то с ФС мы в первую очередь винили его, что отнимало немало времени :). Однако мы планируем миграцию назад на BtrFS, когда он будет признан достаточно стабильным в свободном сообществе. А XFS был плох своей производительностью для наших use case-ов.

Мы использовали /opt для директорий реплицируемых скриптов, а /srv/lxc для контейнеров.

++ Репликация конфигураций хост-систем

Допустим, что вам необходимо делать большое множество vlan-ов для эффективной изоляции различных сервисов. В таких зачастую случаях возникают определённые неприятности, например:
** Долго загружается ОС, особенно Gentoo с "rc_parallel=yes" и hangup-ами (загружаться может часами).
** Огромные конфигурационные файлы, что постоянно вызывает ошибки по "человеческому фактору".
** Иногда нельзя синхронизировать конфигурационные файлы, так как необходимо использовать разные IP-адреса.

Данные проблемы можно решить используя [[https://gitlab.ut.mephi.ru/ut/ipw ipw]], вместо стандартных init.d-скриптов настройки сети.

Все приведённые далее команды необходимо выполнять на обоих рабочих узлах.

Пример глобальной (синхронизируемой) конфигурации ipw:

   cat > /etc/ipw.conf <<EOF
   # vim: set filetype=sh:

   ACCESS_IFACES=(eth2:zabbix eth3:mirrorport)
   D1Q_IFACES=(bond0)

   bond0_SLAVES=(eth0 eth1)
   bond0_MODE=802.3ad

   bond0_VLAN_N_BRIDGES=(
                5:control
                6:storage
              7.1:dbrep-test
              8.1:web-test
              8.2:web-mail
              8.3:web-billing
                9:vpn
                
                 :dbcl-test

                 128

        $(seq 2048 2176)
   )
   
   source /etc/ipw.conf.local
   
   LXC_DIR='/srv/lxc'
   EOF

Данная конфигурационный файл предполагает следующую сетевую конфигурацию:
- Интерфейсы eth0 и eth1 будут объединены в bonding-интерфейс bond0 под алгоритму 802.3ad.
- Интерфейс eth2 будет присоединенён в мост "zabbix", а eth3 будет присоединён в мост "mirrorport".
- Поверх интерфейса bond0 будет созданы dot1q-интерфейсы bond0.5, bond0.6, bond0.7, bond0.7.1, bond0.8, bond0.8.1, bond0.8.2, bond0.8.3, bond0.9.
- Будет выставлен MTU 1496 интерфейсам bond0.7.1, bond0.8.1, bond0.8.2, bond0.8.3.
- Интерфейс bond0.5 будет присоединенён в мост "control", а интерфейсы bond0.6, bond0.7.1, bond0.8.1, bond0.8.2, bond0.8.3, bond0.9 в мосты "storage", "dbrep-test", "web-test", "web-mail", "web-billing" и "vpn" соответственно.
- Будет создан мост "dbcl-test"
- Будет создан мост "vlan128", а также будет создан интерфейс bond0.128 и занесён в мост "vlan128".
- Будут созданы мосты с vlan2048 по vlan2176, а также будут созданы интерфейсы с bond0.2048 по bond0.2176 и занесены в мосты vlan2048 — vlan2176 соответственно.

Пример локальной (несинхронизируемой) конфигурации ipw:
   
   cat > /etc/ipw.conf.local <<EOF
   control_IP=(
            'addr add 10.0.5.2/24'
            'route add default via 10.0.5.1'
   )
   EOF

Данный конфигурационный файл предполагает запуск двух команд:
   ip addr add 10.0.5.2/24 dev control
   ip addr route add default via 10.0.5.1 dev control

В рамках предложенной конфигурации csync2, файл /etc/ipw.conf будет синхронизироваться между узлами кластера, а /etc/ipw.conf.local не будет.

Устанавливаем сам ipw.

   git -C /usr/local clone https://gitlab.ut.mephi.ru/ut/ipw
   ln -s ../ipw/ipw /usr/local/bin/ipw
   # Debian
   ln -s ../../usr/local/ipw/debian/ipw /etc/init.d/ipw
   update-rc.d ipw defaults
   # Gentoo
   ln -s ../../usr/local/ipw/gentoo/ipw /etc/init.d/ipw
   ln -s ../ipw.conf /etc/conf.d/ipw
   rc-update add ipw default


Для синхронизации конфигурационных файлов самым простым и удобным решением мне показалось использовать csync2. Примеры его конфигурации широко [[http://habrahabr.ru/post/120702/ распространены в Интернете]].

Пример конфигурационного файла:

   group egypt {
           host anubis seth;
   
           key /etc/csync2/egypt.key;
   
           include /root/.bashrc /etc/rc.local /etc/rc.conf /etc/local.d/* /etc/backup.* /etc/portage/*/* /etc/make.conf /etc/bash/* /etc/profile /etc/profile.env /etc/profile.d/* /etc/environment /etc/env.d/* /etc/clsync/* /etc/clsync/rules/* /etc/clsync/synchandler/*/* /etc/clsync/hooks/*/* /etc/init.d/lxc /etc/csync2/* /etc/sudoers /etc/sudoers.d/* /etc/hosts /etc/modules /usr/local/bin/* /var/spool/cron/crontabs/* /etc/cron.*/* /etc/sysctl.conf /etc/passwd /etc/group /etc/shadow /root/.ssh/*id /root/.ssh/authorized_keys /etc/ntp.conf /etc/ssh/ssh_config /etc/ssh/sshd_config /etc/rsync* /etc/conf.d/modules /etc/ipw.conf /etc/init.d/ipw /var/lib/layman/make.conf;
   
           auto younger;
   
           action {
                   pattern /etc/rsyncd.*;
                   exec "/etc/init.d/rsyncd restart";
                   logfile "/var/log/csync2_action.log";
                   do-local;
           }
   
           action {
                   pattern /etc/ssh/sshd_config;
                   exec "/etc/init.d/sshd restart";
                   logfile "/var/log/csync2_action.log";
                   do-local;
           }
   
           action {
                   pattern /etc/sysctl.conf;
                   exec "sysctl -f";
                   logfile "/var/log/csync2_action.log";
                   do-local;
           }
   
           action {
                   pattern /var/spool/cron/crontabs/* /etc/cron.*/*;
                   exec "/etc/init.d/vixie-cron restart";
                   logfile "/var/log/csync2_action.log";
                   do-local;
           }
   
           action {
                   pattern /etc/ntp.conf;
                   exec "/etc/init.d/ntpd restart";
                   logfile "/var/log/csync2_action.log";
                   do-local;
           }
   }

++ Окружение для работы с LXC

Для удобной работы с LXC в HA-кластерах наподобие предлагаемого в данной статье, вы можете использовать набор скриптов из репозитория [[https://github.com/mephi-ut/lxc-ha mephi-ut/lxc-ha на GitHub.com]]:

   git -C /opt clone https://gitlab.ut.mephi.ru/ut/ipw
   echo "PATH=/opt/lxc-ha/lxc/bin:$PATH" >> ~/.bashrc
   exec bash

После этого необходимо обеспечить корректную работу данных скриптов:

** Если вы используете Debian:
   mkdir /etc/csync2; ln -s ../csync2.conf /etc/csync2/csync2.conf
** Для дедупликации конфигураций осуществляется завязка на конфигурационный файл lxctl, который находится по пути «/etc/lxctl/lxctl.yaml». Если такой файл у вас отсутствует, то необходимо его создать и настроить. Пример настройки:
   cat > /etc/lxctl/lxctl.yaml << EOF
   ---
   lvm:
     VG: vg00
   paths:
     YAML_CONFIG_PATH: '/etc/lxctl'
     LXC_CONF_DIR: '/srv/lxc'
     ROOT_MOUNT_PATH: '/usr/lib64/lxctl/rootfs'
     TEMPLATE_PATH: '/usr/lib64/lxctl/templates'
     LXC_LOG_PATH: '/var/log/lxc.log'
     LXC_LOG_LEVEL: 'DEBUG'
   check:
     skip_kernel_config_check: 1
   rsync:  
     RSYNC_OPTS: -aH --delete --numeric-ids --exclude 'proc/*' --exclude 'sys/*' -e ssh
   root:
     ROOT_SIZE: 50G
     ROOT_TYPE: lvm
   fs:
     FS: ext4
     FS_OPTS: -b 4096
     FS_MOUNT_OPTS: defaults,barrier=0
   os:
     OS_TEMPLATE: debian-amd64
   set:
     SEARCHDOMAIN: ru
     IFNAME: 'ip'
   list:
     COLUMNS: 'name,disk_free_mb,status,ip,hostname'
** Настройка ssh/sshd. Вы уже к этому моменту настроили csync2, что должно слегка упростить задачу:
   ssh-keygen -t ecdsa -b 521
   cp ~/.ssh/id_ecdsa.pub ~/.ssh/authorized_keys
   echo 'getroot:x:0:0::/root:/bin/bash' >> /etc/passwd
   cat > /etc/ssh/sshd_config << EOF
   # vim: set filetype=sshconfig:
   
   ListenAddress 0.0.0.0
   PasswordAuthentication no
   UsePAM yes
   PrintLastLog no
   Subsystem       sftp    /usr/lib64/misc/sftp-server
   UseDNS no
   PermitRootLogin no
   PasswordAuthentication yes
   RSAAuthentication no
   PubkeyAuthentication yes
   RhostsRSAAuthentication no
   HostbasedAuthentication no
   AllowGroups sshusers
   Match Group sysusers Address 10.0.5.0/24
          MaxAuthTries 1
          PasswordAuthentication no
          PermitRootLogin yes
   Match Group sysusers Address 127.0.0.0/8
          MaxAuthTries 1
          PasswordAuthentication no
          PermitRootLogin yes
   EOF
   cat > /etc/ssh/ssh_config << EOF
   TCPKeepAlive yes
   ControlMaster auto
   Host *
           ControlPath ~/.ssh/master-%r@%h:%p
           ConnectTimeout 1
   EOF
   groupadd sysusers
   groupadd sshusers
   gpasswd -a sysusers getroot
   gpasswd -a sshusers getroot
   gpasswd -a sshusers yourloginhere

В теории, csync2 должен автоматически отсинхронизировать файлы id_ecdsa и authorized_keys, что должно разрешить ssh без авторизации командой:
   ssh getroot@IP_адрес_соседнего_узла
** Прописываем узлы в hosts:
   echo 'anubis		10.0.5.2' >> /etc/hosts
   echo 'seth		10.0.5.3' >> /etc/hosts
** Создаём скрипт backuphost:
   echo 'echo backup'   > /usr/local/bin/backuphost
   chmod +x /usr/local/bin/backuphost
** Установка screen:
   # Debian:
   apt-get install screen
   # Gentoo:
   emerge screen
** Установка и настройка keepalived:
   # Debian:
   apt-get install keepalived
   # Gentoo:
   apt-get install keepalived
   # Anubis:
   cat > /etc/keepalived/keepalived.conf <<EOF
   global_defs {
           router_id anubis.example.org
   }

   vrrp_sync_group ANUBIS {
           group {
                   ANUBIS
           }
   }

   vrrp_instance ANUBIS {
           state MASTER
           interface tech
           lvs_sync_daemon_interface tech
           virtual_router_id 224
           priority 200
           authentication {
                   auth_type PASS
                   auth_pass somekeywordegqQw4K3Aj2x17kS4BIHw8SaoRcEb3k72O0sTY
           }
           virtual_ipaddress {
                   10.0.5.224/24 dev tech
           }
           virtual_routes {
           }
           notify_master /opt/lxc-ha/keepalived/bin/primary-master.sh
           notify_backup /opt/lxc-ha/keepalived/bin/primary-slave.sh
   }

   vrrp_sync_group SETH {
           group {
                   SETH
           }
   }
   
   vrrp_instance SETH {
           state SLAVE
           interface tech
           lvs_sync_daemon_interface tech
           virtual_router_id 225
           priority 100
           authentication {
                   auth_type PASS
                   auth_pass somekeywordegqQw4K3Aj2x17kS4BIHw8SaoRcEb3k72O0sTY
           }
           virtual_ipaddress {
                   10.0.5.225/24 dev tech
           }
           virtual_routes {
           }
           notify_master /opt/lxc-ha/keepalived/bin/secondary-master.sh
           notify_backup /opt/lxc-ha/keepalived/bin/secondary-slave.sh
   }
   EOF
   # Seth:
   cat > /etc/keepalived/keepalived.conf <<EOF
   global_defs {
           router_id poseidon.ut.mephi.ru
   }
   
   vrrp_sync_group ANUBIS {
           group {
                   ANUBIS
           }
   }
   
   vrrp_instance ANIBUS {
           state SLAVE
           interface control
           lvs_sync_daemon_interface control
           virtual_router_id 224
           priority 100
           authentication {
                   auth_type PASS
                   auth_pass somekeywordegqQw4K3Aj2x17kS4BIHw8SaoRcEb3k72O0sTY
           }
           virtual_ipaddress {
                   10.0.5.224/24 dev control
           }
           virtual_routes {
           }
           notify_master /opt/lxc-ha/keepalived/bin/secondary-master.sh
           notify_backup /opt/lxc-ha/keepalived/bin/secondary-slave.sh
   }
   
   vrrp_sync_group SETH {
           group {
                   SETH
           }
   }
   
   vrrp_instance SETH {
           state MASTER
           interface control
           lvs_sync_daemon_interface control
           virtual_router_id 225
           priority 200
           authentication {
                   auth_type PASS
                   auth_pass somekeywordegqQw4K3Aj2x17kS4BIHw8SaoRcEb3k72O0sTY
           }
           virtual_ipaddress {
                   10.0.5.225/24 dev control
           }
           virtual_routes {
           }
           notify_master /opt/lxc-ha/keepalived/bin/primary-master.sh
           notify_backup /opt/lxc-ha/keepalived/bin/primary-slave.sh
   }
   EOF
   # Gentoo, both:
   rc-update add keepalived default
** Добавляем lxc-runned-notify и lxc-setnameofhost в cron:
   crontab -e # добавляем строки:
      PATH=/opt/lxc-ha/lxc/bin:/usr/lib/ccache:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/bin
      * * * * * /opt/lxc/bin/lxc-setnameofhost
      * * * * * /opt/lxc/bin/lxc-runned-notify 2>/dev/null
** Определяем какое значение будет использоваться для 4-го октета MAC-адреса
   echo 00 > /etc/hostmac
** Если Gentoo, то исправляем значение "lxc_path" в /usr/sbin/lxc-create (или делаем обёртку или делаем symlink) на
   lxc_path=/srv/lxc

Грубо говоря, на этом можно закончить настройку самих хост-систем. Далее необходимо реализовать репликацию файлов и БД контейнеров.

++ Репликация файлов контейнеров

Для репликации файлов предлагается использовать [[https://github.com/xaionaro/clsync clsync]]:
   # Debian:
   apt-get install clsync
   # Gentoo:
   layman -a bircoph
   emerge --sync
   emerge =app-admin/clsync-0.3
   # Both:
   mkdir -p /etc/clsync/hooks/lxc /etc/clsync/rules /etc/clsync/synchandler/lxc
   
   cat > /etc/clsync/clsync.conf << EOF
   [lxc-rsyncshell]
   watch-dir               = /srv/lxc/%label%
   background              = 1
   mode                    = rsyncshell
   debug                   = 1
   syslog                  = 1
   full-initialsync        = 1
   rules-file              = /etc/clsync/rules/lxc
   retries                 = 3
   ignore-exitcode         = 23,24
   
   [lxc-brother]
   config-block-inherits   = lxc-rsyncshell
   delay-sync              = 15
   delay-collect           = 15
   sync-handler            = /etc/clsync/synchandler/lxc/brother.sh
   lists-dir               = /dev/shm/clsync-%label%-brother
   exit-hook               = /etc/clsync/hooks/lxc/exit-brother.sh
   pid-file                = /var/run/clsync-%label%-brother.pid
   status-file             = /srv/lxc/%label%/clsync-brother.status
   dump-dir                = /tmp/clsyncdump-%label%-brother
   threading               = safe
   
   [lxc-brother-atomic-sync]
   config-block-inherits   = lxc-brother
   threading               = off
   background              = 0
   exit-on-no-events       = 1
   max-iterations          = 10
   
   [lxc-brother-initialsync]
   config-block-inherits   = lxc-brother
   threading               = off
   background              = 0
   only-initialsync        = 1

   
   [lxc-backup]
   config-block-inherits   = lxc-rsyncshell
   sync-handler            = /etc/clsync/synchandler/lxc/backup.sh
   delay-sync              = 1800
   delay-collect           = 1800
   delay-collect-bigfile   = 43200
   lists-dir               = /dev/shm/clsync-%label%-backup
   exit-hook               = /etc/clsync/hooks/lxc/exit-backup.sh
   pid-file                = /var/run/clsync-%label%-backup.pid
   status-file             = /srv/lxc/%label%/clsync-backup.status
   dump-dir                = /tmp/clsyncdump-%label%-backup
   
   [lxc-backup-copy]
   config-block-inherits   = lxc-brother
   sync-handler            = /etc/clsync/synchandler/lxc/copytobackup.sh
   threading               = off
   background              = 0
   only-initialsync        = 1
   EOF
   
   cat > /etc/clsync/hooks/lxc/exit-brother.sh << EOF
   #!/bin/bash
   LABEL="$1"
   brotherssh rm -f /srv/lxc/"$LABEL"/clsync-brother.status
   EOF
   
   cat > /etc/clsync/hooks/lxc/exit-backup.sh << EOF
   #!/bin/bash
   LABEL="$1"
   brotherssh rm -f /srv/lxc/"$LABEL"/clsync-backup.status
   EOF
   
   cat > /etc/clsync/rules/lxc << EOF
   -f/var/log/.*
   -d/var/lib/mysql
   -f/bin\.000
   -f/rootfs-readonly
   -f/dmesg
   EOF
   
   cat > /etc/clsync/synchandler/lxc/backup.sh << EOF
   #!/bin/bash

   ACTION="$1"
   LABEL="$2"
   ARG0="$3"
   ARG1="$4"
   
   FROM="/srv/lxc/${LABEL}"
   CLUSTERNAME=$(clustername)
   BACKUPHOST=$(backuphost)
   BACKUPMNT="/mnt/backup"
   BACKUPDECR="/decrement/${LABEL}"
   BACKUPMIRROR="rsync://$CLUSTERNAME@$BACKUPHOST/$HOSTNAME/mirror/${LABEL}"
   
   if [ "$CLSYNC_STATUS" = "initsync" ]; then
           STATICEXCLUDE=''
   else
           STATICEXCLUDE='--exclude-from=/etc/clsync/synchandler/lxc/rsync.exclude'
   fi
   
   function rsynclist() {
           LISTFILE="$1"
           EXCLISTFILE="$2"
   
           excludefrom=''
           if [ "$EXCLISTFILE" != "" ]; then
                   excludefrom="--exclude-from=${EXCLISTFILE}"
           fi
   
           if ping -w 1 -qc 5 -i 0.1 $BACKUPHOST > /dev/null; then
                   exec rsync --password-file="/etc/backup.pass" -aH --timeout=3600 --inplace --delete-before $STATICEXCLUDE "$excludefrom" --include-from="${LISTFILE}" --exclude='*' --backup --backup-dir="$BACKUPDECR"/ "$FROM"/ "$BACKUPMIRROR"/ 2>/tmp/clsync-rsync-"$LABEL"-backup.err
           else
                   sleep $[ 3600 + $RANDOM % 1800 ]
                   return 128
           fi
   }
   
   case "$ACTION" in
           rsynclist)
                   rsynclist "$ARG0" "$ARG1"
                   ;;
   esac
   
   exit 0
   EOF
   
   cat > /etc/clsync/synchandler/lxc/copytobackup.sh << EOF
   #!/bin/bash

   ACTION="$1"
   LABEL="$2"
   ARG0="$3"
   ARG1="$4"
   
   FROM="/srv/lxc/${LABEL}"
   CLUSTERNAME=$(clustername)
   BACKUPHOST=$(backuphost)
   BACKUPMNT="/mnt/backup"
   BACKUPMIRROR="rsync://$CLUSTERNAME@$BACKUPHOST/lxc/${LABEL}"
   
   if [ "$CLSYNC_STATUS" = "initsync" ]; then
           STATICEXCLUDE=''
   else
           STATICEXCLUDE='--exclude-from=/etc/clsync/synchandler/lxc/rsync.exclude'
   fi
   
   function rsynclist() {
           LISTFILE="$1"
           EXCLISTFILE="$2"
   
           excludefrom=''
           if [ "$EXCLISTFILE" != "" ]; then
                   excludefrom="--exclude-from=${EXCLISTFILE}"
           fi
   
           if ping -w 1 -qc 5 -i 0.1 $BACKUPHOST > /dev/null; then
                   exec rsync --password-file="/etc/backup.pass" -aH --timeout=3600 --inplace --delete-before $STATICEXCLUDE "$excludefrom" --include-from="${LISTFILE}" --exclude='*' "$FROM"/ "$BACKUPMIRROR"/ 2>/tmp/clsync-rsync-"$LABEL"-backup.err
           else
                   sleep $[ 3600 + $RANDOM % 1800 ]
                   return 128
           fi
   }
   
   case "$ACTION" in
           rsynclist)
                   rsynclist "$ARG0" "$ARG1"
                   ;;
   esac
   
   exit 0
   EOF
   
   cat > /etc/clsync/synchandler/lxc/brother.sh << EOF
   #!/bin/bash -x
   
   ACTION="$1"
   LABEL="$2"
   ARG0="$3"
   ARG1="$4"
   
   BROTHERMNT="/mnt/mirror"
   BROTHERNAME=$(brothername)
   
   CLUSTERNAME=$(clustername)
   
   FROM="/srv/lxc/${LABEL}"
   TO="rsync://${CLUSTERNAME}@${BROTHERNAME}/lxc/${LABEL}"
   
   if [ "$CLSYNC_STATUS" = "initsync" ]; then
        STATICEXCLUDE=''
   else
        STATICEXCLUDE='--exclude-from=/etc/clsync/synchandler/lxc/rsync.exclude'
   fi
   
   
   function rsynclist() {
        LISTFILE="$1"
        EXCLISTFILE="$2"
   
        excludefrom=''
        if [ "$EXCLISTFILE" != "" ]; then
                excludefrom="--exclude-from=${EXCLISTFILE}"
        fi
   
        if ping -w 1 -qc 5 -i 0.1 $BROTHERNAME > /dev/null; then
                exec rsync --password-file="/etc/rsyncd.pass" -aH --timeout=3600 --inplace --delete-before $STATICEXCLUDE "$excludefrom" --include-from="${LISTFILE}" --exclude='*' "$FROM"/ "$TO"/ 2>/tmp/clsync-rsync-"$LABEL"-brother.err
        else
                sleep $[ 3600 + $RANDOM % 1800 ]
                exit 128
        fi
   }
   
   case "$ACTION" in
        rsynclist)
                rsynclist "$ARG0" "$ARG1"
                ;;
   esac
   
   exit 0
   EOF

   cat >/etc/clsync/synchandler/lxc/rsync.exclude << EOF
   sess_*
   nanocacmail/*/*.nexus
   home/mrtg/*.html
   home/mrtg/*.log
   home/mrtg/*.old
   home/mrtg/*.png
   vim/spell/*
   tmp/*
   sys/*
   proc/*
   run/*
   var/tmp/*
   var/lock/*
   var/run/*
   radwtmp
   bitrix/cache/*
   access.log*
   error.log*
   *cache/***
   rootfs-readonly
   dmesg
   EOF

Добавим в cron запуск lxc-clsync-startstop, чтобы перезапускать репликацию (если она отвалилась) на случай разных проблем (например, если второй узел выпадал из сети):
   crontab -e # добавляем строку: * * * * * /opt/lxc/bin/lxc-clsync-startstop

В предложенной конфигурации clsync использует rsync, поэтому настраиваем rsyncd (на рабочих узлах):
   cat > /etc/rsyncd.conf << EOF
   # /etc/rsyncd.conf

   # This line is required by the /etc/init.d/rsyncd script
   pid file = /var/run/rsyncd.pid

   max connections = 128
   log file = /var/log/rsync.log
   timeout = 300

   [lxc]
   path = /srv/lxc
   use chroot = yes
   max connections = 128
   read only = no
   list = yes
   uid = root
   gid = root
   auth users = egypt
   secrets file = /etc/rsyncd.secrets
   strict modes = no
   hosts allow = 10.0.5.0/24
   ignore errors = no
   ignore nonreadable = yes
   transfer logging = yes
   timeout = 300
   refuse options = checksum dry-run
   dont compress = *.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz *.xz
   EOF
   touch /etc/rsyncd.{secrets,pass} /etc/backup.pass
   chmod 600 /etc/rsyncd.{secrets,pass} /etc/backup/pass
   cat > /etc/rsyncd.secrets << EOF
   egypt:passwordhereQRWyisbVNWh1Q
   EOF
   cat > /etc/rsyncd.pass << EOF
   passwordhereQRWyisbVNWh1Q
   EOF
   cat > /etc/backup.pass << EOF
   passwordherevgX9rj8cAm3Es
   EOF
   
На сервере резервного копирования:
   cat > /etc/rsyncd.conf << EOF
   motd file = /etc/rsyncd.motd
   log file  = /var/log/rsyncd.log
   pid file  = /var/run/rsyncd.pid
   lock file = /var/run/rsync.lock

   [lxc]
      path = /srv/lxc
      uid = root
      gid = root
      read only = no
      list = yes
      auth users = egypt
      secrets file = /etc/rsyncd.secrets

   [anubis]
      path = /srv/storage/hosts/anubis
      uid = root
      gid = root
      read only = no
      list = yes
      auth users = egypt
      secrets file = /etc/rsyncd.secrets
   
   [seth]
      path = /srv/storage/hosts/seth
      uid = root
      gid = root
      read only = no
      list = yes
      auth users = egypt
      secrets file = /etc/rsyncd.secrets
   EOF
   cat > /etc/rsyncd.secrets << EOF
   egypt:passwordherevgX9rj8cAm3Es
   EOF
   
На всех трёх узлах:
   # Gentoo:
   rc-update add rsyncd default
   /etc/init.d/rsyncd start
   # Debian:
   echo 'RSYNC_ENABLE=true' >> /etc/default/rsync
   /etc/init.d/rsync start

Теперь необходимо протестировать как производится the репликация. Для начала необходимо создать тестовый контейнер, например:
   lxc-create -t debian -n replicationtest

Если всё сделано верно, то контейнер должен отреплицироваться на соседний узел. На узле, где запущен the контейнер имеет смысл ввести команду «lxc-list»:
   lxc-list

В результате вы увидите что-то вроде:
   g[16:47:48] [root@anubis ~]# lxc-list
   RUNNING
                           replicationtest [ R ][ R ] sdc1   665G         
        
   
   FROZEN
   
   STOPPED

Первая «R» внутри «[ R ]» означает, что контейнер находится в синхронном состоянии на соседнем узле, а вторая «[ R ]» — что узел находится в синхронном состоянии на сервере резервного копирования.
Сами квадратные скобки «[» и «]» означают, что репликация осуществуется с _данного узла_.

Если посмотреть на «lxc-list» со стороны соседнего узла, то можно увидеть:
   g[17:01:01] [root@seth ~]# lxc-list
   RUNNING      
   
   FROZEN
   
   STOPPED        
                   remote  replicationtest   R    R   sdc1   665G    

Как можно заметить, скобок «[» и «]» на данной стороне не наблюдается. Однако можно увидеть слово «remote» перед именем контейнера, что означает, что контейнер уже запущен на соседнем узле.

Кроме того, можно ввести команду:
   lxc-auto replicationtest

Тогда в «lxc-list» перед именем контейнера появится ключевое слово «auto», что означает в свою очередь, что данный контейнер будет запускаться автоматически при запуске узла. Предполагается, что «lxc-auto» будет использоваться только для нерезервируемых узлов (например узлы СУБД, которые используют другие средства репликации).


++ Репликация БД

Достаточно долгие эксперименты с master-slave- и master-master-репликациями привели нас к выводу, что придётся использовать galera. И опять же долгие эксперименты с galera привели нас к связке percona+galera.

Создаём контейнер с Debian
   lxc-create -t debian -n percona_test

Редактируем конфиг (хотя лучше этот процесс автоматизовать под ваши use case-ы), например:
   cat > /srv/lxc/percona_test/config << EOF
   ## Container
   lxc.utsname                             = percona_test
   lxc.rootfs                              = /srv/lxc/percona_test/rootfs
   lxc.arch                                = x86_64
   lxc.console                             = /srv/lxc/percona_test/console.log
   lxc.tty                                 = 6
   lxc.pts                                 = 128
   
   ## Capabilities
   lxc.cap.drop                            = audit_control
   lxc.cap.drop                            = audit_write
   lxc.cap.drop                            = linux_immutable
   lxc.cap.drop                            = mac_admin
   lxc.cap.drop                            = mac_override
   lxc.cap.drop                            = setpcap
   lxc.cap.drop                            = sys_admin
   lxc.cap.drop                            = sys_boot
   lxc.cap.drop                            = sys_module
   lxc.cap.drop                            = sys_pacct
   lxc.cap.drop                            = sys_rawio
   lxc.cap.drop                            = sys_time
   ## Devices
   # Allow all devices
   #lxc.cgroup.devices.allow               = a
   # Deny all devices
   lxc.cgroup.devices.deny                 = a
   # Allow to mknod all devices (but not using them)
   lxc.cgroup.devices.allow                = c *:* m
   lxc.cgroup.devices.allow                = b *:* m
   
   # /dev/console
   lxc.cgroup.devices.allow                = c 5:1 rwm
   # /dev/fuse
   #lxc.cgroup.devices.allow               = c 10:229 rwm
   # /dev/loop*
   #lxc.cgroup.devices.allow               = b 7:* rwm
   # /dev/loop-control
   #lxc.cgroup.devices.allow               = c 10:237 rwm
   # /dev/null
   lxc.cgroup.devices.allow                = c 1:3 rwm
   # /dev/ptmx
   lxc.cgroup.devices.allow                = c 5:2 rwm
   # /dev/pts/*
   lxc.cgroup.devices.allow                = c 136:* rwm
   # /dev/random
   lxc.cgroup.devices.allow                = c 1:8 rwm
   # /dev/rtc
   lxc.cgroup.devices.allow                = c 254:0 rwm
   # /dev/tty
   lxc.cgroup.devices.allow                = c 5:0 rwm
   # /dev/urandom
   lxc.cgroup.devices.allow                = c 1:9 rwm
   # /dev/zero
   lxc.cgroup.devices.allow                = c 1:5 rwm

   ## Limits
   lxc.cgroup.cpu.shares                  = 8
   lxc.cgroup.cpuset.cpus                 = 0-1
   lxc.cgroup.memory.limit_in_bytes        = 2G
   #lxc.cgroup.memory.memsw.limit_in_bytes = 2G

   ## Filesystem
   lxc.mount.entry                         = proc proc proc ro,nodev,noexec,nosuid 0 0
   lxc.mount.entry                         = sysfs sys sysfs ro 0 0
   lxc.mount.entry                         = tmpfs run/shm tmpfs defaults,size=67108864 0 0
   lxc.mount.entry                         = tmpfs tmp tmpfs defaults,size=67108864 0 0

   ## Network
   lxc.network.type                        = veth
   lxc.network.flags                       = up
   lxc.network.hwaddr                      = f2:00:00:00:00:00
   lxc.network.link                        = dbrep-test
   lxc.network.name                        = cluster
   lxc.network.veth.pair                   = dbtest-r

   lxc.network.type                        = veth
   lxc.network.flags                       = up
   lxc.network.hwaddr                      = f2:00:00:00:00:01
   lxc.network.link                        = dbcl-test
   lxc.network.name                        = client
   lxc.network.veth.pair                   = dbtest-c

   lxc.network.type                        = veth
   lxc.network.flags                       = up
   lxc.network.hwaddr                      = f2:00:00:00:00:02
   #lxc.network.link                        = 
   lxc.network.name                        = res0
   lxc.network.veth.pair                   = db-test-res0
   
   lxc.network.type                        = veth
   lxc.network.flags                       = up
   lxc.network.hwaddr                      = f2:00:00:00:00:03
   #lxc.network.link                        = 
   lxc.network.name                        = res1
   lxc.network.veth.pair                   = db-test-res1
   
   lxc.network.type = empty
   EOF

Запускаем узел.
   lxc-start -n percona_test -d

Этот узел не будет контейнер не будет реплицироваться, так как его имя начинается на "percona_". После его окончательной подготовки, будет необходимо его откопировать на соседний узел и на сервер резервного копирования.

Настраиваем содержимое контейнера (предполагается, что на хост-системе в данной момент времени есть выход в Интернет):
   ifconfig dbcl-test 192.168.0.1/24
   sysctl net.ipv4.conf.dbcl-test.forwarding=1
   iptables -t nat -A POSTROUTING -s 192.168.0.0/24 -j MASQUERADE
   lxc-attach -n percona_test
   cat >> /etc/network/interfaces << EOF
   auto cluster
   iface cluster inet static
           address 192.168.0.2
           netmask 255.255.255.0
   
   auto client
   iface client inet static
           address 192.168.1.1
           netmask 255.255.255.0
   
   EOF
   cat > /etc/apt/sources.d/percona.list << EOF
   deb http://repo.percona.com/apt wheezy main
   deb-src http://repo.percona.com/apt wheezy main
   EOF
   apt-get update
   apt-get -y install percona-xtradb-cluster-full-56 percona-xtradb-cluster-galera-3.x percona-xtrabackup
   cat >> /etc/mysql/my.cnf << EOF
   binlog_format = ROW
   wsrep_provider=/usr/lib/libgalera_smm.so
   wsrep_cluster_name="galera_particle"
   #wsrep_cluster_address="gcomm://"
   wsrep_cluster_address="gcomm://192.168.0.2,192.168.0.3,192.168.0.4"
   wsrep_sst_method=xtrabackup
   wsrep_node_address=192.168.0.2
   wsrep_sst_auth=xtrabackup:yzuW1yuRr3hn28DHPMpHFEc
   wsrep_replicate_myisam=ON
   wsrep_max_ws_rows = 2000
   
   [sst]
   streamfmt=xbstream
   transferfmt=socat
   sockopt=,nodelay,sndbuf=4096576,rcvbuf=4096576
   
   [xtrabackup]
   compact
   parallel=4
   rebuild-threads=2
   EOF
   exit
   
Зачистка (на хост-системе):
   iptables -t nat -D POSTROUTING -s 192.168.0.0/24 -j MASQUERADE
   sysctl net.ipv4.conf.dbcl-test.forwarding=0
   ifconfig dbcl-test 0.0.0.0 down

Останавливаем и копируем контейнер:
   lxc-stop -n percona_test
   clsync -K lxc-brother-initialsync -l percona_test
   clsync -K lxc-backup-copy -l percona_test

На втором узле и сервере резервного копирования исправляем файлы внутри данного контейнера:
   /etc/network/interfaces
   /etc/mysql/my.cnf

Запускаем на всех серверах данный контейнер:
   lxc-start -d -n percona_test
   
Если всё сделано верно, то после запуска, данные инстанции percona должны образовать синхронный master-master-master кластер. И если ввести команду "SHOW STATUS" на любом из данных SQL-серверов, то должно быть отображено нечто наподобие:
   | wsrep_local_state_comment                | Synced                                             |
   | wsrep_incoming_addresses                 | 192.168.0.2:3306,192.168.0.3:3306,192.168.0.4:3306 |
   | wsrep_cluster_size                       | 3                                                  |
   | wsrep_cluster_status                     | Primary                                            |

Данные строки сигнализируют о том, что всё в порядке. И к этому моменту у вас имеется синхронный SQL-кластер.

++ Как пользоваться

Далее необходимо создавать контейнеры, которые будут использовать уже созданную тестовую СУБД «percona_test» (или пересоздать другую). Для этого необходимо в клиентских контейнерах создавать интерфейс, который будет присоединяться в мост «dbcl-test».


++ Теория и болталогия

++ Синхронизация конфигурации хост-систем

csync2 — это средство синхронизации файлов через SSL, с использованием SQLite для хранения метаданных. Конкретно в приведённой выше примере конфигурации, csync2 будет запускаться раз в минуту по cron-у и искать те локальные файлы, которые изменились относительно данных в SQLite-таблице. Когда такие данные будут найдены, csync2 будет пытаться их отсинхронизировать к другим участникам csync2-кластера. Если будет обнаружено, что на удалённой стороне файл тоже изменился, то валидной будет считаться так копия, которая изменялась последней (благодаря опции «auto younger»).

csync2 вполне можно заменить на clsync. Но на момент развёртки csync2 в наших системах, clsync ещё не существовал.

Общая схема синхронизации:
   cron  -->  
   [  local csync2  -->  <file scanning/reading> ] --SSL-->  
   [ remote csync2  -->  <file writing>          ]

++ Синхронизация файлов контейнеров

clsync — это утилита основанная на подсистеме ядра «inotify», которая позволяет отлавливать события изменений файлов на ФС. Изначально эта утилита была написана в качестве альтернативы к lsyncd, но потом в процессе адаптации под HPC-кластера и кластера корпоративных сервисов clsync по своему функционалу сильно существенно от lsyncd.

В рамках предложенной выше конфигурации, clsync использует rsync в качестве sync-handler-а, а так же использует исключения живой синхронизации из файла /etc/clsync/rules/lxc. Более подробная информация описана в «man 1 clsync».

Общая схема запуска:
   Начало синхронизации контейнера при его запуске:
           lxc-start -> lxc-clsync-startstop -> clsync
   Восстановление синхронизации в случае внештатных ситуаций:
                cron -> lxc-clsync-startstop -> clsync
   "Атомарная" синхронизация для перезапуска контейнера на соседнем узле:
                        lxc-rerun-on-brother -> clsync
     lxc-pickup-stop -> lxc-rerun-on-brother -> clsync

Общая схема синхронизации:
   kernel [inotify]  -->  
   clsync            -->  
   [  local rsync --> <file scanning/reading> ] --rsync--> 
   [ remote rsync --> <file scanning/writing> ]


++ Синхронизация БД

Самый простой способ осуществлять SQL-репликацию — это использовать [[http://dev.mysql.com/doc/refman/5.0/en/replication.html штатные средства MySQL (и подобных) для репликации данных]]. Однако данные средства обладают существенными недостатками:
** предполагается использование схем master-slave, так как при master-master могут возникать ситуации останавливающие репликацию;
** на практике не очень удобно.

В результате мы остановились на связке percona server + galera cluster.

[[http://www.percona.com/mysql-5.6-solutions-from-percona Percona server]] — это форк MySQL, поддерживающий подсистему хранения данных XtraDB (как замена InnoDB). 

[[http://galeracluster.com/products/ Galera]] — это, грубо говоря, модуль для MySQL (и подобных), обеспечивающий репликацию «galera replication».

Galera replication — это метод репликации, обеспечивающий возможность организации active-active multi-master кластеров.

Совместно данные технологии работают по [[http://galeracluster.com/documentation-webpages/architecture.html следующей цепочке]]:

Percona Server -> wsrep hooks -> galera plugin -> система коммуникаций с другими узлами

В качестве системы коммуникаций в рамках данной статьи использовался «gcomm».

++ Резервное копирование файлов

В рамках данной конфигурации, кластер будет держать запоздало-синхронную копию на сервере резервного копирования в директорию mirror и откладывать старые экземпляры изменённых файлов в директорию decrement. Раз в неделю выполнялась подготовка squashfs-образов (сжатых с xz с дедупликацией) на основе данных из директории mirror, и раз в сутки перемещалась директория "decrement" в архив (с соответствующим timestamp-ом), архивировался каждый файл алгоритмом xz с помощью find и создавалась новую директория "decrement" для следующего "среза".

На примеры скриптов можно посмотреть из репозитория:
   git -C /tmp clone https://gitlab.ut.mephi.ru/ut/backup-scripts

++ Резервное копирование БД

Резервное копирование БД производится обычным mysqldump по cron-у с узла SQL-кластера на сервера резервного копирования (чтобы не блокировать рабочие узлы).

++ Пара слов об ipw

Если вы произвели изменение конфига ipw, то достаточно набрать "ipw fix", чтобы его применить.