こんにちは、Habr! 私は最近、PostgreSQLバージョン9.6サーバーの最も信頼できるクラスターを構成するという課題に直面しました。
計画どおり、任意のサーバーまたは複数のサーバーの損失を経験し、事故後にサーバーを自動的にコミッションできるクラスターを取得したかったのです。
クラスターを計画するとき、PostgreSQLのメインドキュメントとHabrを含むさまざまなハウツーの両方から多くの記事を研究し、RepMgrを使用して標準クラスターを構成し、pgpoolを試しました。
一般的にはうまくいきましたが、時々切り替えに問題が発生したり、事故から回復するために手動の介入が必要になりました。 一般的に、私はより多くのオプションを探すことにしました。
その結果、どこか(正確にはどこにいるかは覚えていません)美しい
Zalando Patroniプロジェクトへのリンクを見つけて、すべてをまとめました...
はじめに
Patroniは、さまざまなタイプのレプリケーションでPostgreSQLクラスターを自動的に提供し、ロールを自動的に切り替えることができるPythonデーモンです。
私の意見では、その特別な美しさは、クラスターとウィザードの選択の関連性を維持するために、分散
DCSリポジトリーが使用されることです(Zookeeper、etcd、Consulによってサポートされます)。
したがって、クラスターはほとんどすべてのシステムに簡単に統合でき、DCSの要求によって、またはhttpを介してPatroniに直接、現在誰がマスターであり、すべてのサーバーのステータスを常に確認できます。
まあ、それはただ美しいです:)
Patroniの作業をテストし、ウィザードと他のサーバーをドロップし、さまざまなデータベースを注ぎ込もうとしました(数分で25 GBのベースが自動的に0から10 GBのネットワークに上昇しました)。 以下に説明するスキームを完全に実装した後、単一のアドレスでデータベースにアクセスする単純なベンチャーでテストし、クラスターのすべての要素(サーバーマスター、haproxy、keepalived)の落下を経験しました。
役割を新しいマスターに転送する際の遅延は数秒でした。 以前のマスターをクラスターに戻すか、新しいサーバーを追加すると、ロールの変更は発生しません。
クラスターの展開を自動化し、新しいサーバーを追加するために、使い慣れたAnsibleを使用することにしました(この記事の最後に、作成されたロールへのリンクを示します)。 DCSとして、すでに使用されているConsulが使用されます。
この記事には2つの主な目標があります:PostgreSQLユーザーにPatroniのような素晴らしいものがあることを示すこと(実際にはRunet全般と特にHabrに言及することはほとんどありません)と同時に、Ansibleを使用して少しの経験を共有します。
Ansibleのロールとプレイブックを解析する例により、すべてのアクションを一度に説明しようとします。 Ansibleを使用しない人は、すべてのアクションを自動サーバー管理用のお気に入りのツールに転送するか、手動で実行できます。
yamlスクリプトのほとんどは長いので、スポイラーでラップします。
ストーリーは、サーバーの準備とクラスター自体の展開の2つの部分に分かれます。
Ansibleに慣れている人にとっては、最初の部分はおもしろくないので、2番目の部分に直接切り替えることをお勧めします。
パートI
この例では、Centos 7ベースの仮想マシンを使用しています。仮想マシンは、定期的に更新されるテンプレート(カーネル、システムパッケージ)からデプロイされますが、このトピックはこの記事の範囲外です。
仮想マシンにアプリケーションまたはサーバーソフトウェアが事前にインストールされていないことに注意してください。 たとえば、AWS、DO、vScaleなどのクラウドリソースも非常に適しています。 それらには、動的インベントリとAnsibleとの統合用のスクリプトがあります。または、Terraformをねじ込むことができるため、サーバーを最初から作成および削除するプロセス全体を自動化できます。
まず、Ansibleの使用済みリソースのインベントリを作成する必要があります。 私にとって(そしてデフォルトでは)Ansibleは/ etc / ansibleにあります。 / etc / ansible / hostsファイルにインベントリを作成します。
[pgsql] cluster-pgsql-01.local cluster-pgsql-02.local cluster-pgsql-03.local
内部ドメインゾーン.localを使用するため、サーバーにはこれらの名前が付けられます。
次に、必要なすべてのコンポーネントと作業ツールをインストールするために、各サーバーを準備する必要があります。
この目的のために、/ etc / ansible / tasksでプレイブックを作成します。
/etc/ansible/tasks/essentialsoftware.yml --- - name: Install essential software yum: name={{ item }} state=latest tags: software with_items: - ntpdate - bzip2 - zip - unzip - openssl-devel - mc - vim - atop - wget - mytop - screen - net-tools - rsync - psmisc - gdb - subversion - htop - bind-utils - sysstat - nano - iptraf - nethogs - ngrep - tcpdump - lm_sensors - mtr - s3cmd - psmisc - gcc - git - python2-pip - python-devel - name: install the 'Development tools' package group yum: name: "@Development tools" state: present
Essentialパッケージのパッケージは、あらゆるサーバーで使い慣れた作業環境を作成するために使用されます。
開発ツールパッケージグループ、いくつかの-develおよびpythonライブラリは、pipがPostgreSQL用のPythonモジュールを構築するために必要です。
VmWare ESXiに基づいた仮想マシンを使用します。管理を容易にするために、vmwareエージェントを実行する必要があります。
これを行うには、オープンエージェントvmtoolsdを起動し、別のプレイブックでそのインストールについて説明します(すべてのサーバーが仮想であるわけではないため、一部のサーバーではこのタスクが必要ない場合があります)。
/etc/ansible/tasks/open-vm-tools.yml --- - name: Install open VM tools for VMWARE yum: name={{ item }} state=latest tags: open-vm-tools with_items: - open-vm-tools - name: VmWare service start and enabling service: name=vmtoolsd.service state=started enabled=yes tags: open-vm-tools
この場合、ソフトウェアの主要部分をインストールするためのサーバーの準備を完了するには、次の手順が必要です。
1)ntpを使用して時刻同期を設定する
2)監視のためにzabbixエージェントをインストールして実行する
3)必要なsshキーとauthorized_keysをロールします。
クラスター自体に関連しない詳細で記事を過度に膨張させないために、これらのタスクを実行するansibleプレイブックを簡単に引用します。
NTP:
/etc/ansible/tasks/ntpd.yml --- - name: setting default timezone set_fact: timezone: name=Europe/Moscow when: timezone is not defined - name: setting TZ timezone: name={{ timezone }} when: timezone is defined tags: - tz - tweaks - ntp - ntpd - name: Configurating cron for ntpdate cron: name="ntpdate" minute="*/5" job="/usr/sbin/ntpdate pool.ntp.org" tags: - tz - tweaks - ntp - ntpd - name: ntpd stop and disable service: name=ntpd state=stopped enabled=no tags: - tz - tweaks - ntp - ntpd ignore_errors: yes - name: crond restart and enabled service: name=crond state=restarted enabled=yes tags: - tz - tweaks - ntp - ntpd
最初に、サーバーに個人用タイムゾーンが設定されているかどうかが確認され、設定されていない場合はモスクワが設定されます(そのようなサーバーの大部分があります)。
ESXi仮想マシンでの時間変動に問題があるため、ntpdを使用しません。その後、ntpdは時間の同期を拒否します。 (そして、いじくり回すパニック0は役に立ちません)。 したがって、クラウンでntpクライアントを5分に1回起動するだけです。
Zabbixエージェント:
/etc/ansible/tasks/zabbix.yml --- - name: set zabbix ip external set_fact: zabbix_ip: 132.xx.xx.98 tags: zabbix - name: set zabbix ip internal set_fact: zabbix_ip: 192.168.xx.98 when: ansible_all_ipv4_addresses | ipaddr('192.168.0.0/16') tags: zabbix - name: Import Zabbix3 repo yum: name=http://repo.zabbix.com/zabbix/3.0/rhel/7/x86_64/zabbix-release-3.0-1.el7.noarch.rpm state=present tags: zabbix - name: Remove old zabbix yum: name=zabbix2* state=absent tags: zabbix - name: Install zabbix-agent software yum: name={{ item }} state=latest tags: zabbix with_items: - zabbix-agent - zabbix-release - name: Creates directories file: path={{ item }} state=directory tags: - zabbix - zabbix-mysql with_items: - /etc/zabbix/externalscripts - /etc/zabbix/zabbix_agentd.d - /var/lib/zabbix - name: Copy scripts copy: src=/etc/ansible/templates/zabbix/{{ item }} dest=/etc/zabbix/externalscripts/{{ item }} owner=zabbix group=zabbix mode=0755 tags: zabbix with_items: - netstat.sh - iostat.sh - iostat2.sh - iostat_collect.sh - iostat_parse.sh - php_workers_discovery.sh - name: Copy .my.cnf copy: src=/etc/ansible/files/mysql/.my.cnf dest=/var/lib/zabbix/.my.cnf owner=zabbix group=zabbix mode=0700 tags: - zabbix - zabbix-mysql - name: remove default configs file: path={{ item }} state=absent tags: zabbix with_items: - /etc/zabbix_agentd.conf - /etc/zabbix/zabbix_agentd.conf - name: put zabbix-agentd.conf to default place template: src=/etc/ansible/templates/zabbix/zabbix_agentd.tpl dest=/etc/zabbix_agentd.conf owner=zabbix group=zabbix force=yes tags: zabbix - name: link zabbix-agentd.conf to /etc/zabbix file: src=/etc/zabbix_agentd.conf dest=/etc/zabbix/zabbix_agentd.conf state=link tags: zabbix - name: zabbix-agent start and enable service: name=zabbix-agent state=restarted enabled=yes tags: zabbix
Zabbixをインストールするとき、エージェント設定はテンプレートからロールされます;サーバーアドレスのみを変更する必要があります。
ネットワーク内にあるサーバーは192.168.x.98にアクセスし、アクセスできないサーバーは同じサーバーの実際のアドレスに移動します。
sshキーの転送とsshの設定は、たとえばansible-galaxyにある別のロールに移動されました。
多くのオプションがあり、変更の本質は非常に簡単であるため、ここですべての内容を引用する意味はありません。
作成された構成をサーバーにロールします。 一般に、私はすべてのコンポーネントとクラスター自体のインストールをワンステップで実行しますが、すでに完全な設定をしていますが、このチュートリアルの目的のために、それぞれの章で2つのステップに分割する方が良いと思われます
サーバーのグループ用のプレイブックを作成します。
/etc/ansible/cluster-pgsql.yml --- - hosts: pgsql pre_tasks: - name: Setting system hostname hostname: name="{{ ansible_host }}" - include: tasks/essentialsoftware.yml - include: tasks/open-vm-tools.yml - include: tasks/ntpd.yml post_tasks: - include: tasks/zabbix.yml roles: - ssh.role - ansible-role-patroni
すべてのサーバーの処理を開始します。
非表示のテキスト〜#ansible-playbook cluster-pgsql.yml --skip-tags patroni
githubリポジトリから私の例を完全にダウンロードした場合、Patroniの役割も持つことになりますが、これはまだ解決する必要はありません。
--skip-tags引数により、Ansibleはこのタグでマークされたステップをスキップするため、ansible-role-patroniロールは現在実行されません。
ディスク上にない場合、ひどいことは何も起こりません。アニスブルはこのキーを無視します。
Ansibleはすぐにルートとしてサーバーにログインします。権限のないユーザーとしてansibleを実行する必要がある場合は、ルート権限を必要とするステップに特別なフラグ「become:true」を追加する必要があります。
準備は終わりました。
パートII
クラスターを直接デプロイします。
クラスターの設定(PostgreSQLとすべてのコンポーネントのインストール、それらの個別の設定の入力)には多くの作業が必要なため、このプロセス全体を別のロールで選びました。
Ansibleのロールを使用すると、一連の隣接タスクをグループ化できるため、スクリプトの記述と稼働状態でのメンテナンスが簡単になります。
Patroniをインストールするためのロールテンプレートは、
https :
//github.com/gitinsky/ansible-role-patroniです。作成者に感謝します。
私の目的のために、既存のものを作り直し、haproxyとkeepalivedプレイブックを追加しました。
私の役割は/ etc / ansible / rolesディレクトリにあります。 新しいロールのディレクトリとそのコンポーネントのサブディレクトリを作成します。
~# mkdir /etc/ansible/roles/ansible-role-patroni/tasks ~# mkdir /etc/ansible/roles/ansible-role-patroni/templates
PostgreSQLに加えて、クラスタは次のコンポーネントで構成されます。
1)サーバーのステータスを監視し、リクエストをマスターサーバーにリダイレクトするhaproxy。
2)keepalivedを使用して、クラスターへの単一のエントリポイント-仮想IPを確保します。
デフォルトでansibleによって実行されるファイルに、このロールによって実行されるすべてのプレイブックをリストします。
/etc/ansible/roles/ansible-role-patroni/tasks/main.yml - include: postgres.yml - include: haproxy.yml - include: keepalived.yml
次に、個々のタスクについて説明します。
最初のプレイブックでは、ネイティブリポジトリからPostgreSQL 9.6、およびPatroniが必要とする追加パッケージをインストールし、GitHubからPatroni自体をダウンロードします。
/etc/ansible/roles/ansible-role-patroni/tasks/postgres.yml --- - name: Import Postgresql96 repo yum: name=https://download.postgresql.org/pub/repos/yum/9.6/redhat/rhel-7-x86_64/pgdg-centos96-9.6-3.noarch.rpm state=present tags: patroni when: install is defined - name: Install PGsql96 yum: name={{ item }} state=latest tags: patroni with_items: - postgresql96 - postgresql96-contrib - postgresql96-server - python-psycopg2 - repmgr96 when: install is defined - name: checkout patroni git: repo=https://github.com/zalando/patroni.git dest=/opt/patroni tags: patroni when: install is defined - name: create /etc/patroni file: state=directory dest=/etc/patroni tags: patroni when: install is defined - name: put postgres.yml template: src=postgres0.yml dest=/etc/patroni/postgres.yml backup=yes tags: patroni when: install is defined - name: install python packages pip: name={{ item }} tags: patroni with_items: - python-etcd - python-consul - dnspython - boto - mock - requests - six - kazoo - click - tzlocal - prettytable - PyYAML when: install is defined - name: put patroni.service systemd unit template: src=patroni.service dest=/etc/systemd/system/patroni.service backup=yes tags: patroni when: install is defined - name: Reload daemon definitions command: /usr/bin/systemctl daemon-reload tags: patroni - name: restart service: name=patroni state=restarted enabled=yes tags: patroni
ソフトウェアのインストールに加えて、このプレイブックは現在のPatroniサーバーの構成もアップロードします。systemdにはシステムにデーモンを起動するためのユニットがあり、その後でPatroniデーモンを起動します。 構成テンプレートとsystemdユニットは、ロール内のテンプレートディレクトリに存在する必要があります。
Patroni構成テンプレート:
/etc/ansible/roles/ansible-role-patroni/templates/postgres.yml.j2 name: {{ patroni_node_name }} scope: &scope {{ patroni_scope }} consul: host: consul.services.local:8500 restapi: listen: 0.0.0.0:8008 connect_address: {{ ansible_default_ipv4.address }}:8008 auth: 'username:{{ patroni_rest_password }}' bootstrap: dcs: ttl: &ttl 30 loop_wait: &loop_wait 10 maximum_lag_on_failover: 1048576 # 1 megabyte in bytes postgresql: use_pg_rewind: true use_slots: true parameters: archive_mode: "on" wal_level: hot_standby archive_command: mkdir -p ../wal_archive && cp %p ../wal_archive/%f max_wal_senders: 10 wal_keep_segments: 8 archive_timeout: 1800s max_replication_slots: 5 hot_standby: "on" wal_log_hints: "on" pg_hba: # Add following lines to pg_hba.conf after running 'initdb' - host replication replicator 192.168.0.0/16 md5 - host all all 0.0.0.0/0 md5 postgresql: listen: 0.0.0.0:5432 connect_address: {{ ansible_default_ipv4.address }}:5432 data_dir: /var/lib/pgsql/9.6/data pg_rewind: username: superuser password: {{ patroni_postgres_password }} pg_hba: - host all all 0.0.0.0/0 md5 - hostssl all all 0.0.0.0/0 md5 replication: username: replicator password: {{ patroni_replicator_password }} network: 192.168.0.0/16 superuser: username: superuser password: {{ patroni_postgres_password }} admin: username: admin password: {{ patroni_postgres_password }} restore: /opt/patroni/patroni/scripts/restore.py
各クラスターサーバーには個別のPatroni構成が必要なため、その構成はjinja2テンプレート(postgres0.yml.j2ファイル)の形式であり、テンプレートステップでは、このテンプレートを変数の置換で強制的に変換できます。
インベントリ内でクラスタ全体に共通の変数を直接示します。これは次の形式になります。
/ etc / ansible /ホスト [pgsql] cluster-pgsql-01.local cluster-pgsql-02.local cluster-pgsql-03.local [pgsql:vars] patroni_scope: "cluster-pgsql" patroni_rest_password: flsdjkfasdjhfsd patroni_postgres_password: flsdjkfasdjhfsd patroni_replicator_password: flsdjkfasdjhfsd cluster_virtual_ip: 192.xx.xx.125 </spoiler> - host_vars/_: <spoiler title="/etc/ansible/host_vars/pgsql-cluster-01.local/main.yml"> <source lang="yaml"> patroni_node_name: cluster_pgsql_01 keepalived_priority: 99
なぜいくつかの変数が必要なのかを解読します。
patroni_scope-Consulに登録するときのクラスター名
patroni_node_name-Consulへの登録時のサーバー名
patroni_rest_password-Patroni httpインターフェイスのパスワード(クラスターを変更するコマンドを送信するために必要)
patroni_postgres_password:postgresユーザーのパスワード。 新しいベースがpatroniによって作成された場合にインストールされます。
patroni_replicator_password-ユーザーレプリケーターのパスワード。 彼に代わって、スレーブへの複製が実行されます。
また、このファイルには、他のプレイブックまたはロールで使用される他の変数がリストされています。特に、ssh設定(キー、ユーザー)、サーバーのタイムゾーン、keepalivedクラスターのサーバー優先順位などがあります。
他のサーバーの構成も同様で、サーバー名と優先順位はそれに応じて変更されます(たとえば、3つのサーバーの場合は99-100-101)。
haproxyをインストールして構成します。
/etc/ansible/roles/ansible-role-patroni/tasks/haproxy.yml --- - name: Install haproxy yum: name=} state=latest tags: - patroni - haproxy with_items: - haproxy when: install is defined - name: put config template: src=haproxy.cfg.j2 dest=/etc/haproxy/haproxy.cfg backup=yes tags: - patroni - haproxy - name: restart and enable service: name=haproxy state=restarted enabled=yes tags: - patroni - haproxy
Haproxyは各ホストにインストールされ、その構成にすべてのPostgreSQLサーバーへのリンクが含まれ、どのサーバーが現在マスターであるかを確認し、要求を送信します。
このテストでは、Patroniの素晴らしい機能であるRESTインターフェースが使用されます。
サーバーにアクセスする場合:8008 URL(8008がデフォルトポート)Patroniはjsonのクラスターの状態に関するレポートを返し、サーバーがマスターであるかどうかの応答コードhttpも反映します。 そうである場合、コード200の応答があります。そうでない場合、コード503の応答があります。
Patroniのドキュメントを参照することを強くお勧めします。httpインターフェイスは非常に興味深いものであり、ロールを強制的に切り替えたり、クラスターを管理したりすることもできます。
同様に、これはPatroniのpatronyctl.pyコンソールユーティリティを使用して実行できます。
haproxyの設定は非常に簡単です:
/etc/ansible/roles/ansible-role-patroni/templates/haproxy.cfg global maxconn 800 defaults log global mode tcp retries 2 timeout client 30m timeout connect 4s timeout server 30m timeout check 5s frontend ft_postgresql bind *:5000 default_backend postgres-patroni backend postgres-patroni option httpchk http-check expect status 200 default-server inter 3s fall 3 rise 2 server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008 server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008 server {{ patroni_node_name }} {{ patroni_node_name }}.local:5432 maxconn 300 check port 8008
この構成によれば、haproxyはポート5000でリッスンし、そこからマスターサーバーにトラフィックを送信します。
ステータスのチェックは1秒間隔で行われ、サーバーをダウンに転送するには3回の失敗した応答(コード500)、サーバーを切り替えるには2回の成功した応答(コード200)が必要です。
いつでも、任意のhaproxyに直接接続でき、トラフィックはマスターサーバーに正しく転送されます。
Patroniには、confdデーモンを構成するためのテンプレートとetcdとの統合の例も含まれています。これにより、新しいサーバーを削除または追加するときにhaproxy構成を動的に変更できます。
私はまだかなり静的なクラスターを作成していますが、この状況での不必要な自動化、私見は予期しない問題につながる可能性があります。
クライアントの特殊なロジックの変更、活気のあるサーバーの追跡などが必要です。 必須ではなかったため、keepalivedを使用してクラスターへの単一のエントリポイントを作成します。
keepalivedデーモンは、その近隣でvrrpプロトコルを使用し、デーモンの1つをメインのデーモンとして選択した結果(優先順位はconfigで指定され、各サーバーのhost_varsのkeepalived_priority変数にテンプレート化されます)、仮想IPアドレスを上げます。
残りの悪魔は辛抱強く待ちます。 現在のメインkeepalivedサーバーが何らかの理由で死んだり、近隣に事故を知らせたりすると、再選挙が行われ、次に優先順位の高いサーバーが仮想IPアドレスを取得します。
haproxyのクラッシュから保護するために、keepalivedデーモンはkillall -0 haproxyコマンドを1秒に1回実行してチェックを実行します。 haproxyプロセスがある場合はコード0を返し、ない場合は1を返します。
haproxyが消えると、keepalivedデーモンはvrrpを介してクラッシュを通知し、仮想IPを削除します。
仮想IPは、ライブhaproxyを使用して、次に優先順位の高いサーバーをすぐに選択します。
keepalivedをインストールして構成します。
/etc/ansible/roles/ansible-role-patroni/tasks/keepalived.yml --- - name: Install keepalived yum: name=} state=latest tags: - patroni - keepalived with_items: - keepalived when: install is defined - name: put alert script template: src=alert.sh.j2 dest=/usr/local/sbin/alert.sh backup=yes mode=755 tags: - patroni - keepalived when: install is defined - name: put config template: src=keepalived.conf.j2 dest=/etc/keepalived/keepalived.conf backup=yes tags: - patroni - keepalived - name: restart and enable service: name=keepalived state=restarted enabled=yes tags: - patroni - keepalived
keepalivedのインストールに加えて、このプレイブックは、電報を介してアラートを送信するための簡単なスクリプトもコピーします。 スクリプトはメッセージを変数の形式で受け取り、curl番目のテレグラムAPIを取得します。
このスクリプトでは、アラートを送信するためにトークンとテレグラムグループIDを指定するだけです。
keepalivedの構成は、jinja2テンプレートとして説明されています。
/etc/ansible/roles/ansible-role-patroni/templates/keepalived.conf.j2 global_defs { router_id {{ patroni_node_name }} } vrrp_script chk_haproxy { script "killall -0 haproxy" interval 1 weight -20 debug fall 2 rise 2 } vrrp_instance {{ patroni_node_name }} { interface ens160 state BACKUP virtual_router_id 150 priority {{ keepalived_priority }} authentication { auth_type PASS auth_pass secret_for_vrrp_auth } track_script { chk_haproxy weight 20 } virtual_ipaddress { {{ cluster_virtual_ip }}/32 dev ens160 } notify_master "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became MASTER'" notify_backup "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became BACKUP'" notify_fault "/usr/bin/sh /usr/local/sbin/alert.sh '{{ patroni_node_name }} became FAULT'" }
変数patroni_node_name、cluster_virtual_ip、およびkeepalived_priorityは、host_varsからの対応するデータを変換します。
keepalived構成には、ステータスの変更に関するメッセージを電報チャネルに送信するためのスクリプトも含まれています。
完全なクラスター構成をサーバーにロールします。
~# ansible-playbook cluster-pgsql.yml
Ansibleはべき等であるため、つまり 以前に完了していない場合にのみステップを実行し、追加のパラメーターなしでプレイブックを開始できます。
長く待てない場合、またはサーバーの準備が完全に整っていることが確実な場合は、-t patroniスイッチを使用してansible-playbookを実行できます。
その後、Patroniロールのステップのみが実行されます。
サーバーの役割(マスターまたはスレーブ)を個別に示していないことに注意してください。 この構成により、空のデータベースが作成され、最初に構成されたサーバーが単にマスターになります。
新しいサーバーを追加すると、PatroniはDCSを介してクラスターマスターが既に存在することを確認し、現在のマスターからデータベースを自動的にコピーし、スレーブをそれに接続します。
しばらくウィザードの背後にあるスレーブを起動すると、Patroniはpg_rewindを使用して変更を自動的にアップロードします。
すべてのサーバーが開始され、役割が選択されていることを確認します。
~# journalctl -f -u patroni
スレーブからのメッセージ(サーバーcluster-pgsql-01):
ネタバレFeb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,254 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_01
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_01
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: does not have lock
Feb 17 23:50:32 cluster-pgsql-01.local patroni.py[100626]: 2017-02-17 23:50:32,255 INFO: no action. i am a secondary and i am following a leader
ウィザードからのメッセージ(この場合、サーバーはcluster-pgsql-02です):
ネタバレFeb 17 23:52:23 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:23,457 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:23 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:23,874 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:24 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:24,082 INFO: no action. i am the leader with the lock
Feb 17 23:52:33 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:33,458 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:33 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:33,884 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:52:34 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:52:34,094 INFO: no action. i am the leader with the lock
ログは、各サーバーがそのステータスとマスターのステータスを常に監視していることを明確に示しています。
マスターを停止してみましょう。
~# systemctl stop patroni
ネタバレFeb 17 23:54:03 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:03,457 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:54:03 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:03,880 INFO: Lock owner: cluster_pgsql_02; I am cluster_pgsql_02
Feb 17 23:54:04 cluster-pgsql-02.local patroni.py[4913]: 2017-02-17 23:54:04,092 INFO: no action. i am the leader with the lock
Feb 17 23:54:11 cluster-pgsql-02.local systemd[1]: Stopping Runners to orchestrate a high-availability PostgreSQL...
Feb 17 23:54:13 cluster-pgsql-02.local patroni.py[4913]: waiting for server to shut down.... done
Feb 17 23:54:13 cluster-pgsql-02.local patroni.py[4913]: server stopped
しかし、その瞬間にスレーブで何が起こったのか:
ネタバレFeb 17 19:54:12 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:12,353 INFO: does not have lock
Feb 17 19:54:12 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:12,776 INFO: no action. i am a secondary and i am following a leader
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,440 WARNING: request failed: GET http://192.xx.xx.121:8008/patroni (HTTPConnectionPool(host='192.xx.xx.121', port=8008
): Max retries exceeded with url: /patroni (Caused by NewConnectionError('<requests.packages.urllib3.connection.HTTPConnection object at 0x1f12750>: Failed to establish a new connection: [Er
rno 111] Connection refused',)))
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,444 INFO: Got response from cluster_pgsql_03 http://192.xx.xx.122:8008/patroni: {"database_system_identifier": "63847
30077944883705", "postmaster_start_time": "2017-02-17 05:36:52.388 MSK", "xlog": {"received_location": 34997272728, "replayed_timestamp": null, "paused": false, "replayed_location": 34997272
728}, "patroni": {"scope": "clusters-pgsql", "version": "1.2.3"}, "state": "running", "role": "replica", "server_version": 90601}
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: server promoting
Feb 17 19:54:13 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:13,961 INFO: cleared rewind flag after becoming the leader
Feb 17 19:54:14 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:14,179 INFO: promoted self to leader by acquiring session lock
Feb 17 19:54:23 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:23,436 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_01
Feb 17 19:54:23 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:23,857 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_01
Feb 17 19:54:24 cluster-pgsql-01 patroni.py: 2017-02-17 23:54:24,485 INFO: no action. i am the leader with the lock
このサーバーは、マスターの役割を傍受しました。
次に、サーバー2をクラスターに戻します。
~# systemctl start patroni
ネタバレ見出しFeb 18 00:02:11 cluster-pgsql-02.local systemd[1]: Started Runners to orchestrate a high-availability PostgreSQL.
Feb 18 00:02:11 cluster-pgsql-02.local systemd[1]: Starting Runners to orchestrate a high-availability PostgreSQL...
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,186 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,190 WARNING: Postgresql is not running.
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,190 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,398 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,400 INFO: starting as a secondary
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,412 INFO: rewind flag is set
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,609 INFO: changing primary_conninfo and restarting in progress
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:13,631 INFO: running pg_rewind from user=superuser host=192.xx.xx.120 port=5432 dbname=postgres sslmode=prefer sslcompression=1
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: servers diverged at WAL position 8/26000098 on timeline 25
Feb 18 00:02:13 cluster-pgsql-02.local patroni.py[56855]: rewinding from last common checkpoint at 8/26000028 on timeline 25
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: Done!
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:14,535 INFO: postmaster pid=56893
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: < 2017-02-18 00:02:14.554 MSK > LOG: redirecting log output to logging collector process
Feb 18 00:02:14 cluster-pgsql-02.local patroni.py[56855]: < 2017-02-18 00:02:14.554 MSK > HINT: Future log output will appear in directory "pg_log".
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: localhost:5432 - accepting connections
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: localhost:5432 - accepting connections
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,790 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: Lock owner: cluster_pgsql_01; I am cluster_pgsql_02
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: does not have lock
Feb 18 00:02:15 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:15,791 INFO: establishing a new patroni connection to the postgres cluster
Feb 18 00:02:16 cluster-pgsql-02.local patroni.py[56855]: 2017-02-18 00:02:16,014 INFO: no action. i am a secondary and i am following a leader
Patroniは、既存のマスターを使用してクラスターに接続し、データベースを現在の状態に更新していることを発見し、スレーブの役割を正しく引き継ぎました。
メインkeepalivedサーバーでhaproxyを停止して、クラスターの別のレイヤーでエラーを作成してみましょう。
優先順位により、2番目のサーバーはこの役割を受け入れます。
[root@cluster-pgsql-02 ~]# ip a
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:a9:b8:7b brd ff:ff:ff:ff:ff:ff
inet 192.xx.xx.121/24 brd 192.168.142.255 scope global ens160
valid_lft forever preferred_lft forever
inet 192.xx.xx.125/32 scope global ens160 <----
valid_lft forever preferred_lft forever
inet6 fe80::xxx::4895:6d90/64 scope link
valid_lft forever preferred_lft forever
haproxyを停止します。
~# systemctl stop haproxy ; journalctl -fl
Feb 18 00:18:54 cluster-pgsql-02.local Keepalived_vrrp[25018]: VRRP_Script(chk_haproxy) failed
Feb 18 00:18:56 cluster-pgsql-02.local Keepalived_vrrp[25018]: VRRP_Instance(cluster_pgsql_02) Received higher prio advert
Feb 18 00:18:56 cluster-pgsql-02.local Keepalived_vrrp[25018]: VRRP_Instance(cluster_pgsql_02) Entering BACKUP STATE
Feb 18 00:18:56 cluster-pgsql-02.local Keepalived_vrrp[25018]: VRRP_Instance(cluster_pgsql_02) removing protocol VIPs.
Feb 18 00:18:56 cluster-pgsql-02.local Keepalived_vrrp[25018]: Opening script file /usr/bin/sh
Feb 18 00:18:56 cluster-pgsql-02.local Keepalived_healthcheckers[25017]: Netlink reflector reports IP 192.xx.xx.125 removed
Keepalivedは問題をキャッチし、仮想アドレスを自身から削除し、それについて近隣に信号を送りました。
2番目のサーバーで何が起こったのかを見てみましょう。
Feb 18 00:18:56 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) forcing a new MASTER election
Feb 18 00:18:56 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) forcing a new MASTER election
Feb 18 00:18:56 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) forcing a new MASTER election
Feb 18 00:18:56 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) forcing a new MASTER election
Feb 18 00:18:57 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Transition to MASTER STATE
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Entering MASTER STATE
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) setting protocol VIPs.
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: Opening script file /usr/bin/sh
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Received lower prio advert, forcing new election
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_healthcheckers[41189]: Netlink reflector reports IP 192.xx.xx.125 added
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Received lower prio advert, forcing new election
Feb 18 00:18:58 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125
Feb 18 00:19:03 cluster-pgsql-01.local Keepalived_vrrp[41190]: VRRP_Instance(cluster_pgsql_01) Sending gratuitous ARPs on ens160 for 192.xx.xx.125
再選出が2回行われたため(クラスターの3番目のサーバーが最初の選出前にアナウンスを送信できたため)、サーバー1がリーダーの役割を引き受け、仮想IPを設定しました。
私たちはこれを確信しています:
[root@cluster-pgsql-01 log]# ip a
2: ens160: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP qlen 1000
link/ether 00:50:56:a9:f0:90 brd ff:ff:ff:ff:ff:ff
inet 192.xx.xx.120/24 brd 192.xx.xx.255 scope global ens160
valid_lft forever preferred_lft forever
inet 192.xx.xx.125/32 scope global ens160 <---- !
valid_lft forever preferred_lft forever
inet6 fe80::1d75:40f6:a14e:5e27/64 scope link
valid_lft forever preferred_lft forever
仮想IPは、レプリケーションマスターではないサーバーに存在するようになりました。 ただし、haproxyを介してデータベースにアクセスし、クラスターの状態を個別に監視し、常にマスターに要求を送信するため、これは重要ではありません。
haproxyがシステムに返されると、再選出が再び行われ(最高の優先順位でキープアライブが動作するようになります)、仮想IPがその場所に戻ります。
まれに、スレーブがマスターに追い付かないことがあります(たとえば、かなり前に落ちて、walログが部分的にリタイアすることがありました)。 この場合、スレーブのベースを使用してディレクトリを完全にクリアできます。
「Rm -rf /var/lib/pgsql/9.6/data」、Patroniを再起動します。 彼女はベースとマスターを完全にマージします。
(「不要な」データベースをクリーンアップする際の注意、コマンドを実行しているサーバーに注意してください!!!)この場合、patronictlユーティリティを使用する必要があります。 reinitコマンドを使用すると、特定のクラスターノードを安全にクリーニングできますが、ウィザードでは実行されません。
CyberDemonを追加していただきありがとうございます。
patronictlユーティリティ自体を使用すると、DCSにアクセスせずに、コマンドラインからクラスターの現在の状況を確認し、クラスターを管理できます。
クラスターステータスレポートの例:
/opt/patroni/patronictl.py -c /etc/patroni/postgres.yml list cluster-pgsql:
+ --------------- + ------------------ + -------------- --- + -------------- + ------------------ + ----------- +
| クラスター| メンバー| ホスト| 役割| 州| MBのラグ|
+ --------------- + ------------------ + -------------- --- + -------------- + ------------------ + ----------- +
| cluster-pgsql | cluster_pgsql_01 | 192.xxx.xxx.120 | リーダー| ランニング| 0.0 |
| cluster-pgsql | cluster_pgsql_02 | 192.xxx.xxx.121 | 同期スタンバイ| ランニング| 0.0 |
| cluster-pgsql | cluster_pgsql_03 | 192.xxx.xxx.122 | | レプリカの作成| 33712.0 |
+ --------------- + ------------------ + -------------- --- + -------------- + ------------------ + ----------- +
この場合、3番目のノードが注がれ、マスターからの遅延は33 GBです。
このプロセスを完了すると、ゼロラグで実行状態になります。
Stateフィールドが空であることにも注意してください。
これは、私の場合のクラスターが同期モードで動作するためです。同期レプリケーションの遅延を減らすために、一方のスレーブは同期モードで動作し、もう一方は通常の非同期で動作します。マスターが消えると、ロールがシフトし、2番目のスレーブがマスターになった最初のスレーブと同期モードに入ります。あとがき
私の意見では、このクラスターが幸福に十分ではない唯一のことは、接続プーリングとすべてのスレーブへの読み取り要求のプロキシーで読み取りパフォーマンスを向上させ、ウィザードのみに要求を挿入および更新することです。非同期レプリケーションを使用する構成では、読み込み負荷をアンロードすると予期しない応答が発生する可能性があります。スレーブがマスターより遅れている場合、これを考慮する必要があります。ストリーミング(非同期)レプリケーションは、常にクラスターの一貫性を提供しません。これには同期レプリケーションが必要です。このモードでは、マスターサーバーはトランザクションのコピーとスレーブへの適用の確認を待機し、データベースの速度が低下します。ただし、トランザクションの損失が許容できない場合(一部の金融アプリケーションなど)、同期レプリケーションが選択されます。Patroniはすべてのオプションをサポートしており、同期レプリケーションがより適している場合は、Patroni configsのいくつかのフィールドの値を変更するだけです。さまざまな複製方法に関する質問は、Patroniのドキュメントに詳しく記載されています。実際、このシステムのすべての機能をカバーするpgpoolを使用することをお勧めします。データベース、プロキシ要求を監視し、仮想IPを設定し、クライアント接続のプーリングも実装できます。はい、彼はこれをすべて行うことができます。しかし、私の意見では、パトロニとのスキームははるかに透明です(もちろんこれは私の意見です)、pgpoolでの実験中に、ウォッチドッグと仮想アドレスで奇妙な動作を見つけました。もちろん、問題が私の手にあるだけであり、後でpgpoolのテストに戻る予定です。ただし、いずれにしても、pgpoolはクラスターを完全に自動的に管理し、新しいサーバーを入力して(特に)失敗したサーバーを返し、DCSを操作することはできません。私の意見では、これはPatroniの最も興味深い機能です。ご清聴ありがとうございました。このソリューションのさらなる改善のための提案をご覧いただき、コメントで質問にお答えします。Zalando Patroniのためにどうもありがとう、そして元のプロジェクトの作者知事 Patroniの基礎を務め、そしてアレックスChistyakov Ansibleのためのテンプレートの役割のために。記事で説明されているプレイブックとAnsibleテンプレートの完全なコードはこちらにあります。 AnsibleとPostgreSQLの達人からの改善に感謝します。 :)
:主な記事や情報源を使用いくつかのオプションクラスターPgSQLは:→ https://habrahabr.ru/post/301370/→ https://habrahabr.ru/post/213409/→ https://habrahabr.ru/company/etagi/を/ 314000 /ブログ→ Zalandoでpatroni上のポストはブログ→ プロジェクトpatroni→ Ansible-役割patroniアレックスChistyakov→ 知事長期凍結の、残念ながら開発を- 。→ Ansble for Devopsは、多数のAnsibleの例を含む素晴らしいチュートリアルです。