Add percona based Galera cluster

Change-Id: I5316e30f3f960348a211189d6689ada596289d5e
This commit is contained in:
Proskurin Kirill 2016-11-17 12:53:07 +00:00
parent 3e30c26171
commit bc59213349
13 changed files with 473 additions and 0 deletions

View File

@ -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

View File

@ -0,0 +1,3 @@
Package: *
Pin: origin "{{ url.percona.debian.repo | host }}"
Pin-Priority: 500

View File

@ -0,0 +1 @@
%microservices ALL=(root) NOPASSWD: /bin/chown mysql\:mysql /var/lib/mysql, /bin/chown mysql\:mysql /var/log/ccp/mysql

View File

@ -0,0 +1,2 @@
# Maria DB repo
deb [arch=amd64,i386] {{ url.percona.debian.repo }} jessie main

View File

@ -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

View File

@ -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"

34
service/files/my.cnf.j2 Normal file
View File

@ -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:"

View File

@ -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

View File

@ -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)

53
service/galera.yaml Normal file
View File

@ -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"

5
tools/yamllint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
set -ex
workdir=$(dirname $0)
yamllint -c $workdir/yamllint.yaml $(find . -not -path '*/\.*' -type f -name '*.yaml')

21
tools/yamllint.yaml Normal file
View File

@ -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

12
tox.ini Normal file
View File

@ -0,0 +1,12 @@
[tox]
minversion = 1.6
envlist = linters
skipsdist = True
[testenv:linters]
deps = yamllint
commands =
{toxinidir}/tools/yamllint.sh
[testenv:venv]
commands = {posargs}