├── password.yaml ├── inventory.ini ├── LICENSE ├── README.md ├── proxysql_admin_config.j2 ├── my.cnf.j2 ├── proxysql_config.j2 └── play.yaml /password.yaml: -------------------------------------------------------------------------------- 1 | mysql_root_password: < this is your percona-xtra-db cluster root password > 2 | proxysql_password: < this is your percona-xtra-db cluster proxysql password > 3 | -------------------------------------------------------------------------------- /inventory.ini: -------------------------------------------------------------------------------- 1 | # ## Configure 'ip' variable to define your servers ip 2 | # ## different ip than the default iface 3 | 4 | [all] 5 | ansible_host= ansible_user= 6 | ansible_host= ansible_user= 7 | ansible_host= ansible_user= 8 | ansible_host= ansible_user= 9 | ansible_host= ansible_user= 10 | 11 | [database_nodes] 12 | ansible_host= ansible_user= 13 | ansible_host= ansible_user= 14 | ansible_host= ansible_user= 15 | 16 | 17 | 18 | [proxysql_nodes] 19 | ansible_host= ansible_user= 20 | ansible_host= ansible_user= 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 shayan amiri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Percona XtraDB Cluster Deployment 2 | 3 | This repository contains Ansible scripts for deploying Percona XtraDB Cluster, a high-performance MySQL clustering solution. 4 | 5 | ## Overview 6 | 7 | This project uses Ansible playbooks to easily set up Percona XtraDB Cluster on multiple nodes, installs ProxySQL, and connects the proxy to the database nodes . 8 | 9 | ## Prerequisites 10 | 11 | Before using this playbook, ensure that the following prerequisites are met: 12 | 13 | - Ansible is installed on the machine running the playbook. 14 | - Servers/nodes are available with root or sudo access. 15 | 16 | ## Getting Started 17 | 18 | ### Ansible Playbook 19 | 20 | 1. Clone this repository to your local machine: 21 | 22 | ```bash 23 | git clone https://github.com/shayanamiri037/Ansible-Xtradb-Cluster 24 | cd Ansible-Xtradb-Cluster 25 | 26 | 2. Define your server nodes in the inventory.ini file: 27 | 28 | ```bash 29 | [all] 30 | node1 ansible_host=your-node1-ip ansible_user=your-ssh-user 31 | node2 ansible_host=your-node2-ip ansible_user=your-ssh-user 32 | node3 ansible_host=your-node3-ip ansible_user=your-ssh-user 33 | . 34 | . 35 | . 36 | 37 | [database_nodes] 38 | node1 ansible_host=your-node1-ip ansible_user=your-ssh-user 39 | node2 ansible_host=your-node2-ip ansible_user=your-ssh-user 40 | node3 ansible_host=your-node3-ip ansible_user=your-ssh-user 41 | . 42 | . 43 | . 44 | 45 | 3. Set your root password and your proxysql password in password.yaml file. 46 | 4. Customize the MySQL configuration in my.cnf.j2 to suit your requirements. 47 | 5. Customize the Proxysql configuration in proxysql_admin_config.j2 and proxysql_config.j2 to suit your requirements. 48 | 6. Run the playbook 49 | 50 | ```bash 51 | ansible-playbook -i inventory.ini -b play.yaml 52 | -------------------------------------------------------------------------------- /proxysql_admin_config.j2: -------------------------------------------------------------------------------- 1 | export PROXYSQL_DATADIR='/var/lib/proxysql' 2 | 3 | # -------------------------------- 4 | # encrypted login credentials file options 5 | # 6 | #export LOGIN_FILE='/path/to/loginfile' 7 | #export LOGIN_PASSWORD_FILE='/path/to/loginfile/password' 8 | 9 | # -------------------------------- 10 | # proxysql admin interface credentials. 11 | # 12 | export PROXYSQL_USERNAME='admin' 13 | export PROXYSQL_PASSWORD='admin' 14 | export PROXYSQL_HOSTNAME='localhost' 15 | export PROXYSQL_PORT='6032' 16 | 17 | # -------------------------------- 18 | # PXC admin credentials for connecting to pxc-cluster-node. 19 | # 20 | export CLUSTER_USERNAME='proxysql' 21 | export CLUSTER_PASSWORD= "{{ passwords.proxysql_password | default('') }}" 22 | export CLUSTER_HOSTNAME='localhost' 23 | export CLUSTER_PORT='3306' 24 | 25 | # -------------------------------- 26 | # proxysql monitoring user. proxysql admin script will create 27 | # this user in pxc to monitor pxc-nodes. 28 | # 29 | export MONITOR_USERNAME='proxysql' 30 | export MONITOR_PASSWORD= "{{ passwords.proxysql_password | default('') }}" 31 | 32 | # -------------------------------- 33 | # add extrauser here to connect to pxc-node through proxysql 34 | 35 | 36 | # -------------------------------- 37 | # ProxySQL hostgroup IDs 38 | # 39 | export WRITER_HOSTGROUP_ID='10' 40 | export READER_HOSTGROUP_ID='11' 41 | export BACKUP_WRITER_HOSTGROUP_ID='12' 42 | export OFFLINE_HOSTGROUP_ID='13' 43 | 44 | # -------------------------------- 45 | # ProxySQL read/write configuration mode. 46 | # 47 | export MODE='singlewrite' 48 | 49 | # -------------------------------- 50 | # max_connections default (used only when INSERTing a new mysql_servers entry) 51 | # 52 | export MAX_CONNECTIONS='1000' 53 | 54 | # -------------------------------- 55 | # Determines the maximum number of writesets a node can have queued 56 | # before the node is SHUNNED to avoid stale reads. 57 | # 58 | export MAX_TRANSACTIONS_BEHIND=100 59 | 60 | # -------------------------------- 61 | # Connections to the backend servers (from ProxySQL) will use SSL 62 | # 63 | export USE_SSL='no' 64 | 65 | # -------------------------------- 66 | # Determines if a node should be added to the reader hostgroup if it has 67 | # been promoted to the writer hostgroup. 68 | # If set to 'yes', then all writers (including backup-writers) are added to 69 | # the read hostgroup. 70 | # If set to 'no', then none of the writers (including backup-writers) are added. 71 | # If set to 'backup', then only the backup-writers will be added to 72 | # the read hostgroup. 73 | # 74 | export WRITERS_ARE_READERS='backup' 75 | -------------------------------------------------------------------------------- /my.cnf.j2: -------------------------------------------------------------------------------- 1 | # Template my.cnf for PXC 2 | # Edit to your requirements. 3 | [client] 4 | socket=/var/run/mysqld/mysqld.sock 5 | 6 | [mysqld] 7 | server-id=1 8 | datadir=/var/lib/mysql 9 | socket=/var/run/mysqld/mysqld.sock 10 | log-error=/var/log/mysql/error.log 11 | #general_log_file = /var/log/mysql/mysql.log 12 | tmpdir = /dev/shm 13 | #general_log = 1 14 | # Disabling pxc-encrypt-cluster-traffic 15 | pxc-encrypt-cluster-traffic=OFF 16 | 17 | pid-file=/var/run/mysqld/mysqld.pid 18 | bind-address = 0.0.0.0 19 | 20 | # Binary log expiration period is 604800 seconds, which equals 7 days 21 | binlog_expire_logs_seconds=259200 22 | 23 | ######## wsrep ############### 24 | # Path to Galera library 25 | wsrep_provider=/usr/lib/galera4/libgalera_smm.so 26 | 27 | # Cluster connection URL contains IPs of nodes 28 | #If no IP is found, this implies that a new cluster needs to be created, 29 | #in order to do that you need to bootstrap this node 30 | wsrep_cluster_address=gcomm://{{ groups['database_nodes'] | map('extract', hostvars, 'ansible_host') | join(',') }} 31 | 32 | # In order for Galera to work correctly binlog format should be ROW 33 | binlog_format=ROW 34 | 35 | # Slave thread to use 36 | #wsrep_slave_threads=8 37 | wsrep_applier_threads=8 38 | wsrep_log_conflicts 39 | 40 | # This changes how InnoDB autoincrement locks are managed and is a requirement for Galera 41 | innodb_autoinc_lock_mode=2 42 | 43 | # Node IP address 44 | wsrep_node_address={{ hostvars[inventory_hostname].ansible_host }} 45 | # Cluster name 46 | wsrep_cluster_name=pxc-cluster 47 | 48 | #If wsrep_node_name is not specified, then system hostname will be used 49 | wsrep_node_name={{ inventory_hostname }} 50 | 51 | #pxc_strict_mode allowed values: DISABLED,PERMISSIVE,ENFORCING,MASTER 52 | pxc_strict_mode=ENFORCING 53 | 54 | # SST method 55 | wsrep_sst_method=xtrabackup-v2 56 | 57 | # Percona Monitoring 58 | #slow_query_log=ON 59 | #log_output=FILE 60 | #long_query_time=0 61 | #log_slow_admin_statements=ON 62 | #log_slow_slave_statements=ON 63 | 64 | #log_slow_rate_limit=100 65 | #log_slow_rate_type='query' 66 | #slow_query_log_always_write_time=1 67 | #log_slow_verbosity='full' 68 | #slow_query_log_use_global_control='all' 69 | 70 | performance_schema=ON 71 | performance-schema-instrument='statement/%=ON' 72 | performance-schema-consumer-statements-digest=ON 73 | innodb_monitor_enable=all 74 | 75 | userstat=ON 76 | 77 | # --------------------------------------------------------------------------------- 78 | # Performance 79 | key_buffer_size = 256M 80 | innodb_buffer_pool_size = 10G # Adjust depending on available RAM 81 | innodb_log_file_size = 512M 82 | innodb_flush_log_at_trx_commit = 2 83 | innodb_flush_method = O_DIRECT 84 | innodb_file_per_table = 1 85 | 86 | # Connections 87 | max_connections = 20000 # Adjust based on expected concurrent connections 88 | max_allowed_packet = 16M 89 | thread_cache_size = 16 90 | 91 | # Temporary Tables 92 | tmp_table_size = 256M 93 | max_heap_table_size = 256M 94 | 95 | # Optimizations 96 | table_open_cache = 400 97 | innodb_flush_neighbors = 0 98 | innodb_thread_concurrency = 8 # Match your CPU core count 99 | #innodb_read_io_threads = 4 100 | #innodb_write_io_threads = 4 101 | -------------------------------------------------------------------------------- /proxysql_config.j2: -------------------------------------------------------------------------------- 1 | #file proxysql.cfg 2 | 3 | ######################################################################################## 4 | # This config file is parsed using libconfig , and its grammar is described in: 5 | # http://www.hyperrealm.com/libconfig/libconfig_manual.html#Configuration-File-Grammar 6 | # Grammar is also copied at the end of this file 7 | ######################################################################################## 8 | 9 | ######################################################################################## 10 | # IMPORTANT INFORMATION REGARDING THIS CONFIGURATION FILE: 11 | ######################################################################################## 12 | # On startup, ProxySQL reads its config file (if present) to determine its datadir. 13 | # What happens next depends on if the database file (disk) is present in the defined 14 | # datadir (i.e. "/var/lib/proxysql/proxysql.db"). 15 | # 16 | # If the database file is found, ProxySQL initializes its in-memory configuration from 17 | # the persisted on-disk database. So, disk configuration gets loaded into memory and 18 | # then propagated towards the runtime configuration. 19 | # 20 | # If the database file is not found and a config file exists, the config file is parsed 21 | # and its content is loaded into the in-memory database, to then be both saved on-disk 22 | # database and loaded at runtime. 23 | # 24 | # IMPORTANT: If a database file is found, the config file is NOT parsed. In this case 25 | # ProxySQL initializes its in-memory configuration from the persisted on-disk 26 | # database ONLY. In other words, the configuration found in the proxysql.cnf 27 | # file is only used to initial the on-disk database read on the first startup. 28 | # 29 | # In order to FORCE a re-initialise of the on-disk database from the configuration file 30 | # the ProxySQL service should be started with "systemctl start proxysql-initial". 31 | # 32 | ######################################################################################## 33 | 34 | datadir="/var/lib/proxysql" 35 | errorlog="/var/lib/proxysql/proxysql.log" 36 | #the admin_credentials must be change for Security Precautions . 37 | admin_variables= 38 | { 39 | admin_credentials="admin:admin" 40 | mysql_ifaces="0.0.0.0:6032" 41 | } 42 | 43 | 44 | 45 | 46 | mysql_variables= 47 | { 48 | threads=4 49 | max_connections=50000 50 | default_query_delay=0 51 | default_query_timeout=36000000 52 | have_compress=true 53 | poll_timeout=2000 54 | interfaces="0.0.0.0:6033" 55 | default_schema="information_schema" 56 | stacksize=1048576 57 | connect_timeout_server=3000 58 | # make sure to configure monitor username and password 59 | monitor_username="proxysql" 60 | monitor_password= "{{ passwords.proxysql_password | default('') }}" 61 | monitor_history=600000 62 | monitor_connect_interval=60000 63 | monitor_ping_interval=1000000 64 | monitor_read_only_interval=1500 65 | monitor_read_only_timeout=500 66 | ping_interval_server_msec=120000 67 | ping_timeout_server=500 68 | commands_stats=true 69 | sessions_sort=true 70 | connect_retries_on_failure=10 71 | } 72 | 73 | # Define Percona XtraDB Cluster nodes 74 | mysql_servers = ( 75 | {% for host in groups.database_nodes %} 76 | { 77 | address = "{{ hostvars[host].ansible_host }}" 78 | port = 3306 79 | hostgroup = 10 # WRITER_HOSTGROUP_ID 80 | max_connections = 20000 81 | weight = 100 82 | }, 83 | { 84 | address = "{{ hostvars[host].ansible_host }}" 85 | port = 3306 86 | hostgroup = 11 # READER_HOSTGROUP_ID 87 | max_connections = 20000 88 | weight = 100 89 | }, 90 | {% endfor %} 91 | ) 92 | 93 | 94 | 95 | # defines all the MySQL users 96 | mysql_users = ( 97 | { 98 | username = "proxysql" 99 | password = "{{ passwords.proxysql_password | default('') }}" 100 | default_hostgroup = 10 101 | active = 1 102 | max_connections = 15000 103 | admin-hash_passwords = false 104 | } 105 | ) 106 | 107 | -------------------------------------------------------------------------------- /play.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install Percona XtraDB Cluster 3 | hosts: database_nodes 4 | become: yes 5 | 6 | tasks: 7 | - name: Update the system 8 | apt: 9 | update_cache: yes 10 | 11 | - name: Install the necessary packages 12 | apt: 13 | name: "{{ item }}" 14 | state: present 15 | loop: 16 | - wget 17 | - gnupg2 18 | - lsb-release 19 | - curl 20 | - python3-pip 21 | 22 | - name: Download and install Percona repository 23 | shell: | 24 | wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb 25 | dpkg -i percona-release_latest.generic_all.deb 26 | pip3 install pymysql 27 | 28 | args: 29 | chdir: /tmp 30 | 31 | - name: Update package cache after adding Percona repository 32 | apt: 33 | update_cache: yes 34 | 35 | - name: Enable the release repository for Percona XtraDB Cluster 36 | shell: percona-release setup pxc80 37 | 38 | - name: Install Percona Xtradb Cluster 39 | shell: DEBIAN_FRONTEND=noninteractive apt install -y percona-xtradb-cluster < /dev/null 40 | 41 | - name: Configure Percona XtraDB Cluster 42 | template: 43 | src: my.cnf.j2 44 | dest: /etc/mysql/mysql.conf.d/mysqld.cnf 45 | 46 | 47 | 48 | 49 | - name: Bootstrap nodes and joining to the cluster 50 | hosts: database_nodes 51 | become: yes 52 | vars: 53 | passwords_file: "password.yaml" 54 | 55 | tasks: 56 | - name: Stop MySQL service on all nodes 57 | service: 58 | name: mysql 59 | state: stopped 60 | 61 | - name: Update safe_to_bootstrap value in grastate.dat 62 | replace: 63 | path: /var/lib/mysql/grastate.dat 64 | regexp: '^safe_to_bootstrap: 0$' 65 | replace: 'safe_to_bootstrap: 1' 66 | when: inventory_hostname == groups['database_nodes'] | first 67 | 68 | 69 | - name: Start Bootstrap service on the first node 70 | command: systemctl start mysql@bootstrap.service 71 | when: inventory_hostname == groups['database_nodes'] | first 72 | 73 | - name: Start MySQL service on other nodes 74 | service: 75 | name: mysql 76 | state: started 77 | when: inventory_hostname != groups['database_nodes'] | first 78 | 79 | - name: Stop Bootstrap service on the first node 80 | command: systemctl stop mysql@bootstrap 81 | when: inventory_hostname == groups['database_nodes'] | first 82 | 83 | - name: Start MySQL service on the first node after bootstrap 84 | service: 85 | name: mysql 86 | state: started 87 | when: inventory_hostname == groups['database_nodes'] | first 88 | 89 | - name: Include passwords from file 90 | include_vars: 91 | file: "{{ passwords_file }}" 92 | name: passwords 93 | 94 | - name: Set MySQL root password 95 | mysql_user: 96 | name: root 97 | password: "{{ passwords.mysql_root_password }}" 98 | login_unix_socket: /var/run/mysqld/mysqld.sock 99 | login_user: root 100 | login_password: "" # Empty password for the initial connection 101 | state: present 102 | when: inventory_hostname == groups['database_nodes'] | first 103 | 104 | 105 | 106 | 107 | - name: Install Proxysql 108 | hosts: proxysql_nodes 109 | become: yes 110 | tasks: 111 | - name: Update the system 112 | apt: 113 | update_cache: yes 114 | 115 | - name: Install the necessary packages 116 | apt: 117 | name: "{{ item }}" 118 | state: present 119 | loop: 120 | - wget 121 | - gnupg2 122 | - lsb-release 123 | - curl 124 | - python3-pip 125 | 126 | - name: Download and install Percona repository 127 | shell: | 128 | wget https://repo.percona.com/apt/percona-release_latest.generic_all.deb 129 | dpkg -i percona-release_latest.generic_all.deb 130 | pip3 install pymysql 131 | 132 | 133 | args: 134 | chdir: /tmp 135 | 136 | - name: Update package cache 137 | apt: 138 | update_cache: yes 139 | 140 | - name: Enable the release repository for Percona XtraDB Cluster 141 | shell: percona-release setup pxc80 142 | 143 | 144 | - name: Install Percona XtraDB Cluster client 145 | apt: 146 | name: percona-xtradb-cluster-client 147 | state: present 148 | 149 | - name: Install ProxySQL 150 | apt: 151 | name: proxysql2 152 | state: present 153 | 154 | 155 | 156 | 157 | - name: configure proxysql nodes 158 | hosts: database_nodes 159 | become: yes 160 | vars: 161 | passwords_file: "password.yaml" 162 | 163 | tasks: 164 | 165 | - name: Include passwords from file 166 | include_vars: 167 | file: "{{ passwords_file }}" 168 | name: passwords 169 | 170 | - name: Create proxysql user 171 | mysql_user: 172 | name: proxysql 173 | password: "{{ passwords.proxysql_password }}" 174 | host: "%" 175 | priv: "*.*:ALL" 176 | login_user: root 177 | login_password: "{{ passwords.mysql_root_password }}" 178 | state: present 179 | loop_control: 180 | loop_var: item 181 | loop: "{{ groups['database_nodes'] }}" 182 | when: item == groups['database_nodes'] | first 183 | 184 | - name: Grant privileges to proxysql user 185 | mysql_user: 186 | name: proxysql 187 | priv: "*.*:ALL" 188 | host: "%" 189 | login_user: root 190 | login_password: "{{ passwords.mysql_root_password }}" 191 | state: present 192 | loop_control: 193 | loop_var: item 194 | loop: "{{ groups['database_nodes'] }}" 195 | when: item == groups['database_nodes'] | first 196 | 197 | 198 | 199 | 200 | - name: configure proxysql nodes 201 | hosts: proxysql_nodes 202 | become: yes 203 | vars: 204 | passwords_file: "password.yaml" 205 | 206 | tasks: 207 | 208 | - name: Include passwords from file 209 | include_vars: 210 | file: "{{ passwords_file }}" 211 | name: passwords 212 | 213 | - name: Configure Proxysql admin Config File 214 | template: 215 | src: proxysql_admin_config.j2 216 | dest: /etc/proxysql-admin.cnf 217 | 218 | - name: Configure ProxySQL Config File 219 | template: 220 | src: proxysql_config.j2 221 | dest: /etc/proxysql.cnf 222 | --------------------------------------------------------------------------------