Add percona based Galera cluster
Change-Id: I5316e30f3f960348a211189d6689ada596289d5e
This commit is contained in:
parent
3e30c26171
commit
bc59213349
|
@ -0,0 +1,18 @@
|
|||
FROM {{ image_spec("base-tools") }}
|
||||
MAINTAINER {{ maintainer }}
|
||||
|
||||
COPY {{ render('sources.list.debian.j2') }} /etc/apt/sources.list.d/percona.list
|
||||
COPY {{ render('apt_preferences.debian.j2') }} /etc/apt/preferences
|
||||
COPY percona_sudoers /etc/sudoers.d/percona_sudoers
|
||||
|
||||
RUN apt-key adv --recv-keys --keyserver {{ url.percona.debian.keyserver }} \
|
||||
{{ url.percona.debian.keyid }} \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y --force-yes --no-install-recommends percona-xtradb-cluster-57 jq \
|
||||
&& pip install --no-cache-dir pymysql \
|
||||
&& chmod 750 /etc/sudoers.d \
|
||||
&& chmod 440 /etc/sudoers.d/percona_sudoers \
|
||||
&& usermod -a -G microservices mysql \
|
||||
&& chown -R mysql: /etc/mysql
|
||||
|
||||
USER mysql
|
|
@ -0,0 +1,3 @@
|
|||
Package: *
|
||||
Pin: origin "{{ url.percona.debian.repo | host }}"
|
||||
Pin-Priority: 500
|
|
@ -0,0 +1 @@
|
|||
%microservices ALL=(root) NOPASSWD: /bin/chown mysql\:mysql /var/lib/mysql, /bin/chown mysql\:mysql /var/log/ccp/mysql
|
|
@ -0,0 +1,2 @@
|
|||
# Maria DB repo
|
||||
deb [arch=amd64,i386] {{ url.percona.debian.repo }} jessie main
|
|
@ -0,0 +1,96 @@
|
|||
#!/bin/bash
|
||||
#
|
||||
# Script to make a proxy (ie HAProxy) capable of monitoring Percona XtraDB
|
||||
# Cluster nodes properly
|
||||
#
|
||||
# Authors:
|
||||
# Raghavendra Prabhu <raghavendra.prabhu@percona.com>
|
||||
# Olaf van Zandwijk <olaf.vanzandwijk@nedap.com>
|
||||
#
|
||||
# Based on the original script from Unai Rodriguez and Olaf
|
||||
# (https://github.com/olafz/percona-clustercheck)
|
||||
#
|
||||
# Grant privileges required:
|
||||
# GRANT PROCESS ON *.* TO 'clustercheckuser'@'localhost' IDENTIFIED BY
|
||||
# 'clustercheckpassword!';
|
||||
set -e
|
||||
|
||||
if [[ $1 == '-h' || $1 == '--help' ]];then
|
||||
echo "Usage: $0 <user> <pass> <available_when_donor=0|1> <log_file> <available_when_readonly=0|1> <defaults_extra_file>"
|
||||
exit
|
||||
fi
|
||||
|
||||
MYSQL_USERNAME=monitor
|
||||
MYSQL_PASSWORD={{ percona.monitor_password }}
|
||||
DISCOVERY_SERVICE={{ address("etcd", etcd.client_port) }}
|
||||
CLUSTER_NAME={{ percona.cluster_name }}
|
||||
AVAILABLE_WHEN_DONOR=${3:-0}
|
||||
ERR_FILE="${4:-/dev/null}"
|
||||
AVAILABLE_WHEN_READONLY=${5:-1}
|
||||
DEFAULTS_EXTRA_FILE=${6:-/etc/my.cnf}
|
||||
|
||||
# CLUSTER_NAME to be set in enviroment
|
||||
# DISCOVERY_SERVICE to be set in enviroment
|
||||
|
||||
#Timeout exists for instances where mysqld may be hung
|
||||
TIMEOUT=10
|
||||
|
||||
EXTRA_ARGS=""
|
||||
if [[ -n "$MYSQL_USERNAME" ]]; then
|
||||
EXTRA_ARGS="$EXTRA_ARGS --user=${MYSQL_USERNAME}"
|
||||
fi
|
||||
if [[ -n "$MYSQL_PASSWORD" ]]; then
|
||||
EXTRA_ARGS="$EXTRA_ARGS --password=${MYSQL_PASSWORD}"
|
||||
fi
|
||||
if [[ -r $DEFAULTS_EXTRA_FILE ]];then
|
||||
MYSQL_CMDLINE="mysql --defaults-extra-file=$DEFAULTS_EXTRA_FILE -nNE --connect-timeout=$TIMEOUT \
|
||||
${EXTRA_ARGS}"
|
||||
else
|
||||
MYSQL_CMDLINE="mysql -nNE --connect-timeout=$TIMEOUT ${EXTRA_ARGS}"
|
||||
fi
|
||||
|
||||
ipaddr=$(hostname -i | awk ' { print $1 } ')
|
||||
hostname=$(hostname)
|
||||
|
||||
while true
|
||||
do
|
||||
#
|
||||
# Perform the query to check the wsrep_local_state
|
||||
#
|
||||
WSREP_STATUS=($($MYSQL_CMDLINE -e "SHOW GLOBAL STATUS LIKE 'wsrep_%';" \
|
||||
2>${ERR_FILE} | grep -A 1 -E 'wsrep_local_state$|wsrep_cluster_status$' \
|
||||
| sed -n -e '2p' -e '5p' | tr '\n' ' '))
|
||||
|
||||
if [[ ${WSREP_STATUS[1]} == 'Primary' && ( ${WSREP_STATUS[0]} -eq 4 || \
|
||||
( ${WSREP_STATUS[0]} -eq 2 && $AVAILABLE_WHEN_DONOR -eq 1 ) ) ]]
|
||||
then
|
||||
|
||||
# Check only when set to 0 to avoid latency in response.
|
||||
if [[ $AVAILABLE_WHEN_READONLY -eq 0 ]];then
|
||||
READ_ONLY=$($MYSQL_CMDLINE -e "SHOW GLOBAL VARIABLES LIKE 'read_only';" \
|
||||
2>${ERR_FILE} | tail -1 2>>${ERR_FILE})
|
||||
|
||||
if [[ "${READ_ONLY}" == "ON" ]];then
|
||||
# Percona XtraDB Cluster node local state is 'Synced', but it is in
|
||||
# read-only mode. The variable AVAILABLE_WHEN_READONLY is set to 0.
|
||||
# => return HTTP 503
|
||||
# Shell return-code is 1
|
||||
curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/?recursive=true -XDELETE >> $ERR_FILE 2>&1
|
||||
fi
|
||||
|
||||
fi
|
||||
# Percona XtraDB Cluster node local state is 'Synced' => return HTTP 200
|
||||
# Shell return-code is 0
|
||||
curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/ipaddr -XPUT -d value="$ipaddr" -d ttl=30 >> $ERR_FILE 2>&1
|
||||
curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/hostname -XPUT -d value="$hostname" -d ttl=30 >> $ERR_FILE 2>&1
|
||||
curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr -XPUT -d ttl=30 -d dir=true -d prevExist=true >> $ERR_FILE 2>&1
|
||||
else
|
||||
# Percona XtraDB Cluster node local state is not 'Synced' => return HTTP
|
||||
# 503
|
||||
# Shell return-code is 1
|
||||
curl http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/?recursive=true -XDELETE >> $ERR_FILE 2>&1
|
||||
fi
|
||||
|
||||
sleep 5
|
||||
|
||||
done
|
|
@ -0,0 +1,18 @@
|
|||
configs:
|
||||
service:
|
||||
database: galera
|
||||
db:
|
||||
root_password: "password"
|
||||
max_timeout: 60
|
||||
percona:
|
||||
cluster_name: "k8scluster"
|
||||
xtrabackup_password: "password"
|
||||
monitor_password: "password"
|
||||
port:
|
||||
cont: 3306
|
||||
url:
|
||||
percona:
|
||||
debian:
|
||||
repo: "http://repo.percona.com/apt"
|
||||
keyserver: "hkp://keyserver.ubuntu.com:80"
|
||||
keyid: "9334A25F8507EFA5"
|
|
@ -0,0 +1,34 @@
|
|||
[mysqld]
|
||||
bind-address = 0.0.0.0
|
||||
port = {{ percona.port.cont }}
|
||||
|
||||
datadir = /var/lib/mysql
|
||||
log-error = /var/log/ccp/mysql/mysql.log
|
||||
max_connections = 10000
|
||||
skip-name-resolve
|
||||
|
||||
character-set-server = utf8
|
||||
collation-server = utf8_general_ci
|
||||
|
||||
default_storage_engine = InnoDB
|
||||
binlog_format = ROW
|
||||
|
||||
innodb_autoinc_lock_mode = 2
|
||||
innodb_buffer_pool_size = 512M
|
||||
innodb_file_per_table = 1
|
||||
innodb_flush_log_at_trx_commit = 2
|
||||
innodb_flush_method = O_DIRECT
|
||||
innodb_io_capacity = 500
|
||||
innodb_read_io_threads = 8
|
||||
innodb_write_io_threads = 8
|
||||
|
||||
open_files_limit = 102400
|
||||
|
||||
wsrep_slave_threads = 4
|
||||
wsrep_cluster_address = gcomm://
|
||||
wsrep_provider = /usr/lib/galera3/libgalera_smm.so
|
||||
|
||||
wsrep_cluster_name = {{ percona.cluster_name }}
|
||||
|
||||
wsrep_sst_method = xtrabackup-v2
|
||||
wsrep_sst_auth = "root:"
|
|
@ -0,0 +1,166 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
# Forward logs to docker log collector
|
||||
exec 1>/proc/1/fd/2 2>/proc/1/fd/2
|
||||
|
||||
MYSQL_ROOT_PASSWORD={{ db.root_password }}
|
||||
DISCOVERY_SERVICE={{ address("etcd", etcd.client_port) }}
|
||||
CLUSTER_NAME={{ percona.cluster_name }}
|
||||
XTRABACKUP_PASSWORD={{ percona.xtrabackup_password }}
|
||||
MONITOR_PASSWORD={{ percona.monitor_password }}
|
||||
CURL="curl -sS"
|
||||
|
||||
# if command starts with an option, prepend mysqld
|
||||
if [ "${1:0:1}" = '-' ]; then
|
||||
CMDARG="$@"
|
||||
fi
|
||||
|
||||
if [ -z "$CLUSTER_NAME" ]; then
|
||||
echo >&2 'Error: You need to specify CLUSTER_NAME'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get config
|
||||
DATADIR="$("mysqld" --verbose --wsrep_provider= --help 2>/dev/null | awk '$1 == "datadir" { print $2; exit }')"
|
||||
|
||||
if [ ! -e "$DATADIR/init.ok" ]; then
|
||||
if [ -z "$MYSQL_ROOT_PASSWORD" -a -z "$MYSQL_ALLOW_EMPTY_PASSWORD" -a -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
|
||||
echo >&2 'error: database is uninitialized and password option is not specified '
|
||||
echo >&2 ' You need to specify one of MYSQL_ROOT_PASSWORD, MYSQL_ALLOW_EMPTY_PASSWORD and MYSQL_RANDOM_ROOT_PASSWORD'
|
||||
exit 1
|
||||
fi
|
||||
rm -rf $DATADIR/*
|
||||
mkdir -p "$DATADIR"
|
||||
ls -la "$DATADIR"/
|
||||
|
||||
echo "Running --initialize-insecure on $DATADIR"
|
||||
mysqld --initialize-insecure
|
||||
echo 'Finished --initialize-insecure'
|
||||
|
||||
mysqld --user=mysql --datadir="$DATADIR" --skip-networking &
|
||||
pid="$!"
|
||||
|
||||
mysql=( mysql --protocol=socket -uroot )
|
||||
|
||||
for i in {30..0}; do
|
||||
if echo 'SELECT 1' | "${mysql[@]}" &> /dev/null; then
|
||||
break
|
||||
fi
|
||||
echo 'MySQL init process in progress...'
|
||||
sleep 1
|
||||
done
|
||||
if [ "$i" = 0 ]; then
|
||||
echo >&2 'MySQL init process failed.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# sed is for https://bugs.mysql.com/bug.php?id=20545
|
||||
mysql_tzinfo_to_sql /usr/share/zoneinfo | sed 's/Local time zone must be set--see zic manual page/FCTY/' | "${mysql[@]}" mysql
|
||||
if [ ! -z "$MYSQL_RANDOM_ROOT_PASSWORD" ]; then
|
||||
MYSQL_ROOT_PASSWORD="$(pwmake 128)"
|
||||
echo "GENERATED ROOT PASSWORD: $MYSQL_ROOT_PASSWORD"
|
||||
fi
|
||||
"${mysql[@]}" <<-EOSQL
|
||||
-- What's done in this file shouldn't be replicated
|
||||
-- or products like mysql-fabric won't work
|
||||
SET @@SESSION.SQL_LOG_BIN=0;
|
||||
CREATE USER 'root'@'%' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}' ;
|
||||
GRANT ALL ON *.* TO 'root'@'%' WITH GRANT OPTION ;
|
||||
ALTER USER 'root'@'localhost' IDENTIFIED BY '${MYSQL_ROOT_PASSWORD}';
|
||||
CREATE USER 'xtrabackup'@'localhost' IDENTIFIED BY '$XTRABACKUP_PASSWORD';
|
||||
GRANT RELOAD,PROCESS,LOCK TABLES,REPLICATION CLIENT ON *.* TO 'xtrabackup'@'localhost';
|
||||
GRANT REPLICATION CLIENT ON *.* TO monitor@'%' IDENTIFIED BY '$MONITOR_PASSWORD';
|
||||
GRANT PROCESS ON *.* TO monitor@localhost IDENTIFIED BY '$MONITOR_PASSWORD';
|
||||
DROP DATABASE IF EXISTS test ;
|
||||
FLUSH PRIVILEGES ;
|
||||
EOSQL
|
||||
if [ ! -z "$MYSQL_ROOT_PASSWORD" ]; then
|
||||
mysql+=( -p"${MYSQL_ROOT_PASSWORD}" )
|
||||
fi
|
||||
|
||||
if [ "$MYSQL_DATABASE" ]; then
|
||||
echo "CREATE DATABASE IF NOT EXISTS \`$MYSQL_DATABASE\` ;" | "${mysql[@]}"
|
||||
mysql+=( "$MYSQL_DATABASE" )
|
||||
fi
|
||||
|
||||
if [ "$MYSQL_USER" -a "$MYSQL_PASSWORD" ]; then
|
||||
echo "CREATE USER '"$MYSQL_USER"'@'%' IDENTIFIED BY '"$MYSQL_PASSWORD"' ;" | "${mysql[@]}"
|
||||
|
||||
if [ "$MYSQL_DATABASE" ]; then
|
||||
echo "GRANT ALL ON \`"$MYSQL_DATABASE"\`.* TO '"$MYSQL_USER"'@'%' ;" | "${mysql[@]}"
|
||||
fi
|
||||
|
||||
echo 'FLUSH PRIVILEGES ;' | "${mysql[@]}"
|
||||
fi
|
||||
|
||||
if [ ! -z "$MYSQL_ONETIME_PASSWORD" ]; then
|
||||
"${mysql[@]}" <<-EOSQL
|
||||
ALTER USER 'root'@'%' PASSWORD EXPIRE;
|
||||
EOSQL
|
||||
fi
|
||||
if ! kill -s TERM "$pid" || ! wait "$pid"; then
|
||||
echo >&2 'MySQL init process failed.'
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo 'MySQL init process done. Ready for start up.'
|
||||
echo
|
||||
fi
|
||||
touch $DATADIR/init.ok
|
||||
|
||||
if [ -z "$DISCOVERY_SERVICE" ]; then
|
||||
cluster_join=$CLUSTER_JOIN
|
||||
else
|
||||
|
||||
echo
|
||||
echo 'Registering in the discovery service'
|
||||
echo
|
||||
|
||||
function join { local IFS="$1"; shift; echo "$*"; }
|
||||
|
||||
# Read the list of registered IP addresses
|
||||
ipaddr=$(hostname -i | awk ' { print $1 } ')
|
||||
hostname=$(hostname)
|
||||
|
||||
$CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/queue/$CLUSTER_NAME -XPOST -d value=$ipaddr -d ttl=60
|
||||
|
||||
#get list of IP from queue
|
||||
i=( $($CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/queue/$CLUSTER_NAME | jq -r '.node.nodes[].value') )
|
||||
|
||||
# this remove my ip from the list
|
||||
i1=${i[@]/$ipaddr}
|
||||
|
||||
# Register the current IP in the discovery service
|
||||
|
||||
# key set to expire in 30 sec. There is a cronjob that should update them regularly
|
||||
$CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/ipaddr -XPUT -d value="$ipaddr" -d ttl=30
|
||||
$CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr/hostname -XPUT -d value="$hostname" -d ttl=30
|
||||
$CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/$ipaddr -XPUT -d ttl=30 -d dir=true -d prevExist=true
|
||||
|
||||
i=( $($CURL http://$DISCOVERY_SERVICE/v2/keys/pxc-cluster/$CLUSTER_NAME/?quorum=true | jq -r '.node.nodes[]?.key' | awk -F'/' '{print $(NF)}') )
|
||||
# this remove my ip from the list
|
||||
i2=${i[@]/$ipaddr}
|
||||
|
||||
# Combine to arrays and remove duplicates
|
||||
OLDIFS="$IFS"
|
||||
IFS=$'\n'
|
||||
combined=(`for R in "${i1[@]}" "${i2[@]}" ; do echo "$R" ; done | sort -du`)
|
||||
IFS="$OLDIFS"
|
||||
|
||||
cluster_join=$(join , $combined )
|
||||
echo "Joining cluster $cluster_join"
|
||||
|
||||
bash /opt/ccp/bin/clustercheckcron monitor monitor 1 /var/lib/mysql/clustercheck.log 1 &
|
||||
|
||||
fi
|
||||
|
||||
mysqld --user=mysql --wsrep_cluster_name=$CLUSTER_NAME \
|
||||
--wsrep_cluster_address="gcomm://$cluster_join" \
|
||||
--wsrep_sst_method=xtrabackup-v2 \
|
||||
--wsrep_sst_auth="xtrabackup:$XTRABACKUP_PASSWORD" \
|
||||
--wsrep_node_address="$ipaddr" \
|
||||
--pxc_strict_mode=PERMISSIVE \
|
||||
$CMDARG
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
|
||||
import pymysql.cursors
|
||||
|
||||
connection = pymysql.connect(host='127.0.0.1',
|
||||
port=3306,
|
||||
user='monitor',
|
||||
password='{{ percona.monitor_password }}',
|
||||
connect_timeout=1,
|
||||
read_timeout=1,
|
||||
cursorclass=pymysql.cursors.DictCursor)
|
||||
|
||||
|
||||
def fetch_data(connection):
|
||||
|
||||
try:
|
||||
with connection.cursor() as cursor:
|
||||
sql = "SHOW STATUS LIKE 'wsrep%'"
|
||||
cursor.execute(sql)
|
||||
data = cursor.fetchall()
|
||||
return data
|
||||
except Exception:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_galera(connection):
|
||||
|
||||
results = {}
|
||||
data = fetch_data(connection)
|
||||
for i in data:
|
||||
results[i['Variable_name']] = i['Value']
|
||||
|
||||
if (results["wsrep_local_state_comment"] != "Synced" or
|
||||
results["wsrep_evs_state"] != "OPERATIONAL" or
|
||||
results["wsrep_connected"] != "ON" or
|
||||
results["wsrep_ready"] != "ON"):
|
||||
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
||||
check_galera(connection)
|
|
@ -0,0 +1,53 @@
|
|||
service:
|
||||
name: {{ service.database }}
|
||||
kind: DaemonSet
|
||||
ports:
|
||||
- {{ percona.port }}
|
||||
containers:
|
||||
- name: galera
|
||||
image: percona
|
||||
probes:
|
||||
readiness: "/opt/ccp/bin/percona_readiness.py"
|
||||
liveness:
|
||||
command: "true"
|
||||
type: "exec"
|
||||
volumes:
|
||||
- name: mysql-logs
|
||||
path: "/var/log/ccp/mysql"
|
||||
type: host
|
||||
readOnly: false
|
||||
- name: mysql-storage
|
||||
path: "/var/lib/mysql"
|
||||
type: host
|
||||
readOnly: false
|
||||
pre:
|
||||
- name: chown-logs-dir
|
||||
command: "sudo /bin/chown mysql:mysql /var/log/ccp/mysql"
|
||||
- name: chown-data-dir
|
||||
command: "sudo /bin/chown mysql:mysql /var/lib/mysql"
|
||||
daemon:
|
||||
files:
|
||||
- entrypoint
|
||||
- mycnf
|
||||
- check
|
||||
- readiness
|
||||
dependencies:
|
||||
- etcd
|
||||
command: /opt/ccp/bin/entrypoint.sh
|
||||
|
||||
files:
|
||||
entrypoint:
|
||||
path: /opt/ccp/bin/entrypoint.sh
|
||||
content: percona_entrypoint.sh.j2
|
||||
perm: "0755"
|
||||
mycnf:
|
||||
path: /etc/mysql/my.cnf
|
||||
content: my.cnf.j2
|
||||
check:
|
||||
path: /opt/ccp/bin/clustercheckcron
|
||||
content: clustercheckcron.j2
|
||||
perm: "0755"
|
||||
readiness:
|
||||
path: /opt/ccp/bin/percona_readiness.py
|
||||
content: percona_readiness.py.j2
|
||||
perm: "0750"
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
set -ex
|
||||
|
||||
workdir=$(dirname $0)
|
||||
yamllint -c $workdir/yamllint.yaml $(find . -not -path '*/\.*' -type f -name '*.yaml')
|
|
@ -0,0 +1,21 @@
|
|||
extends: default
|
||||
|
||||
rules:
|
||||
braces:
|
||||
max-spaces-inside: 1
|
||||
comments:
|
||||
level: error
|
||||
comments-indentation:
|
||||
level: warning
|
||||
document-end:
|
||||
present: no
|
||||
document-start:
|
||||
level: error
|
||||
present: no
|
||||
empty-lines:
|
||||
max: 1
|
||||
max-start: 0
|
||||
max-end: 0
|
||||
line-length:
|
||||
level: warning
|
||||
max: 120
|
Loading…
Reference in New Issue