Adding MariaDB

This commit is contained in:
pprokop 2016-11-18 16:34:36 +01:00
parent b321e6792f
commit fdeb41df20
21 changed files with 779 additions and 0 deletions

21
mariadb/.helmignore Normal file
View File

@ -0,0 +1,21 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

4
mariadb/Chart.yaml Normal file
View File

@ -0,0 +1,4 @@
apiVersion: v1
description: A helm chart for mariadb
name: mariadb
version: 0.1.0

2
mariadb/README.md Normal file
View File

@ -0,0 +1,2 @@
Please remember to label nodes with control_node_label from values.yaml
And remember that number of control nodes should be odd.

View File

@ -0,0 +1,56 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: bootstrap-db
data:
bootstrap-db.sh: |
#!/bin/sh
set -ex
SLEEP_TIMEOUT=5
# Initialize system .Values.database.
mysql_install_db --datadir=/var/lib/mysql
# Start mariadb and wait for it to be ready.
mysqld_safe --defaults-file=/etc/my.cnf \
--console \
--wsrep-new-cluster \
--wsrep_cluster_address='gcomm://' \
--bind-address='127.0.0.1' \
--wsrep_node_address='127.0.0.1' \
--wsrep_provider_options='gcache.size=512M; gmcast.listen_addr=tcp://127.0.0.1:{{ .Values.network.port.wsrep }}' &
TIMEOUT=120
while [[ ! -f /var/lib/mysql/mariadb.pid ]]; do
if [[ ${TIMEOUT} -gt 0 ]]; then
let TIMEOUT-=1
sleep 1
else
exit 1
fi
done
# Reset permissions.
# kolla_security_reset requires to be run from home directory
cd /var/lib/mysql ; DB_ROOT_PASSWORD="{{ .Values.database.root_password }}" kolla_security_reset
mysql -u root --password="{{ .Values.database.root_password }}" --port="{{ .Values.network.port.mariadb }}" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' IDENTIFIED BY '{{ .Values.database.root_password }}' WITH GRANT OPTION;"
mysql -u root --password="{{ .Values.database.root_password }}" --port="{{ .Values.network.port.mariadb }}" -e "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '{{ .Values.database.root_password }}' WITH GRANT OPTION;"
# Restart .Values.database.
mysqladmin -uroot -p"{{ .Values.database.root_password }}" --port="{{ .Values.network.port.mariadb }}" shutdown
# Wait for the mariadb server to shut down
SHUTDOWN_TIMEOUT=60
while [[ -f /var/lib/mysql/mariadb.pid ]]; do
if [[ ${SHUTDOWN_TIMEOUT} -gt 0 ]]; then
let SHUTDOWN_TIMEOUT-=1
sleep 1
else
echo "MariaDB instance couldn't be properly shut down"
exit 1
fi
done

View File

@ -0,0 +1,13 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-charsets
data:
charsets.cnf: |+
[mysqld]
character_set_server=utf8
collation_server=utf8_unicode_ci
skip-character-set-client-handshake
[client]
default_character_set=utf8

View File

@ -0,0 +1,10 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-engine
data:
engine.cnf: |+
[mysqld]
default-storage-engine=InnoDB
innodb=FORCE
binlog_format=ROW

View File

@ -0,0 +1,12 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-mycnf
data:
my.cnf: |+
[mysqld]
datadir=/var/lib/mysql
basedir=/usr
[client-server]
!includedir /etc/my.cnf.d/

View File

@ -0,0 +1,17 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-log
data:
log.cnf: |+
[mysqld]
slow_query_log=off
slow_query_log_file=/var/log/mysql/mariadb-slow.log
log_warnings=2
# General logging has huge performance penalty therefore is disabled by default
general_log=off
general_log_file=/var/log/mysql/mariadb-error.log
long_query_time=3
log_queries_not_using_indexes=on

View File

@ -0,0 +1,133 @@
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: mariadb
spec:
selector:
matchLabels:
galera: enabled
template:
metadata:
labels:
app: mariadb
galera: enabled
spec:
nodeSelector:
{{ .Values.deployment.control_node_label }}: enabled
# TODO(DTadrzak): it must be removed in the future
securityContext:
runAsUser: 0
containers:
- name: mariadb
image: {{ .Values.deployment.image }}
imagePullPolicy: Always
env:
- name: INTERFACE_NAME
value: "eth0"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: COMMAND
value: "bash /tmp/start.sh"
- name: DEPENDENCY_CONFIG
value: "/etc/my.cnf.d/wsrep.cnf"
ports:
- containerPort: {{ .Values.network.port.mariadb }}
readinessProbe:
exec:
command:
- python
- /mariadb-readiness.py
volumeMounts:
- name: mycnfd
mountPath: /etc/my.cnf.d
- name: startsh
mountPath: /tmp/start.sh
subPath: start.sh
- name: bootstrapdb
mountPath: /tmp/bootstrap-db.sh
subPath: bootstrap-db.sh
- name: peer-finder
mountPath: /tmp/peer-finder.py
subPath: peer-finder.py
- name: charsets
mountPath: /etc/my.cnf.d/charsets.cnf
subPath: charsets.cnf
- name: engine
mountPath: /etc/my.cnf.d/engine.cnf
subPath: engine.cnf
- name: log
mountPath: /etc/my.cnf.d/log.cnf
subPath: log.cnf
- name: mycnf
mountPath: /etc/my.cnf
subPath: my.cnf
- name: networking
mountPath: /etc/my.cnf.d/networking.cnf
subPath: networking.cnf
- name: pid
mountPath: /etc/my.cnf.d/pid.cnf
subPath: pid.cnf
- name: tuning
mountPath: /etc/my.cnf.d/tuning.cnf
subPath: tuning.cnf
- name: wsrep
mountPath: /configmaps/wsrep.cnf
- name: mysql
mountPath: /var/lib/mysql
- name: replicas
mountPath: /tmp/replicas.py
subPath: replicas.py
- name: readiness
mountPath: /mariadb-readiness.py
subPath: mariadb-readiness.py
volumes:
- name: mycnfd
emptyDir: {}
- name: startsh
configMap:
name: mariadb-startsh
- name: bootstrapdb
configMap:
name: bootstrap-db
- name: peer-finder
configMap:
name: mariadb-peer-finder
- name: charsets
configMap:
name: mariadb-charsets
- name: engine
configMap:
name: mariadb-engine
- name: log
configMap:
name: mariadb-log
- name: mycnf
configMap:
name: mariadb-mycnf
- name: networking
configMap:
name: mariadb-networking
- name: pid
configMap:
name: mariadb-pid
- name: tuning
configMap:
name: mariadb-tuning
- name: wsrep
configMap:
name: mariadb-wsrep
- name: replicas
configMap:
name: mariadb-replicas
- name: readiness
configMap:
name: mariadb-readiness
- name: mysql
hostPath:
path: /var/lib/mysql-openstack-{{ .Values.database.cluster_name }}

View File

@ -0,0 +1,33 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-readiness
data:
mariadb-readiness.py: |+
#!/usr/bin/env python
import os
import sys
import time
import pymysql
DB_HOST = "127.0.0.1"
DB_PORT = int(os.environ.get('MARIADB_SERVICE_PORT', '3306'))
while True:
try:
pymysql.connections.Connection(host=DB_HOST, port=DB_PORT,
connect_timeout=1)
sys.exit(0)
except pymysql.err.OperationalError as e:
code, message = e.args
if code == 2003 and 'time out' in message:
print('Connection timeout, sleeping')
time.sleep(1)
continue
if code == 1045:
print('Mysql ready to use. Exiting')
sys.exit(0)
# other error
raise

View File

@ -0,0 +1,110 @@
apiVersion: batch/v1
kind: Job
metadata:
name: mariadb-seed
spec:
template:
metadata:
labels:
app: mariadb
spec:
restartPolicy: Never
terminationGracePeriodSeconds: 10000
containers:
- name: mariadb-init
image: {{ .Values.deployment.image }}
imagePullPolicy: Always
env:
- name: INTERFACE_NAME
value: "eth0"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: COMMAND
value: "bash /tmp/seed.sh"
- name: DEPENDENCY_CONFIG
value: "/etc/my.cnf.d/wsrep.cnf"
ports:
- containerPort: {{ .Values.network.port.mariadb }}
volumeMounts:
- name: mycnfd
mountPath: /etc/my.cnf.d
- name: seedsh
mountPath: /tmp/seed.sh
subPath: seed.sh
- name: bootstrapdb
mountPath: /tmp/bootstrap-db.sh
subPath: bootstrap-db.sh
- name: peer-finder
mountPath: /tmp/peer-finder.py
subPath: peer-finder.py
- name: charsets
mountPath: /etc/my.cnf.d/charsets.cnf
subPath: charsets.cnf
- name: engine
mountPath: /etc/my.cnf.d/engine.cnf
subPath: engine.cnf
- name: log
mountPath: /etc/my.cnf.d/log.cnf
subPath: log.cnf
- name: mycnf
mountPath: /etc/my.cnf
subPath: my.cnf
- name: networking
mountPath: /etc/my.cnf.d/networking.cnf
subPath: networking.cnf
- name: pid
mountPath: /etc/my.cnf.d/pid.cnf
subPath: pid.cnf
- name: tuning
mountPath: /etc/my.cnf.d/tuning.cnf
subPath: tuning.cnf
- name: wsrep
mountPath: /configmaps/wsrep.cnf
- name: replicas
mountPath: /tmp/replicas.py
subPath: replicas.py
volumes:
- name: mycnfd
emptyDir: {}
- name: seedsh
configMap:
name: mariadb-seedsh
- name: bootstrapdb
configMap:
name: bootstrap-db
- name: peer-finder
configMap:
name: mariadb-peer-finder
- name: charsets
configMap:
name: mariadb-charsets
- name: engine
configMap:
name: mariadb-engine
- name: log
configMap:
name: mariadb-log
- name: mycnf
configMap:
name: mariadb-mycnf
- name: networking
configMap:
name: mariadb-networking
- name: pid
configMap:
name: mariadb-pid
- name: tuning
configMap:
name: mariadb-tuning
- name: wsrep
configMap:
name: mariadb-wsrep
- name: replicas
configMap:
name: mariadb-replicas

View File

@ -0,0 +1,9 @@
apiVersion: v1
kind: Service
metadata:
name: mariadb
spec:
ports:
- port: {{ .Values.network.port.mariadb }}
selector:
app: mariadb

View File

@ -0,0 +1,20 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-networking
data:
networking.cnf: |+
[mysqld]
bind_address=0.0.0.0
port={{ .Values.network.port.mariadb }}
# When a client connects, the server will perform hostname resolution,
# and when DNS is slow, establishing the connection will become slow as well.
# It is therefore recommended to start the server with skip-name-resolve to
# disable all DNS lookups. The only limitation is that the GRANT statements
# must then use IP addresses only.
skip_name_resolve
[client]
protocol=tcp
port={{ .Values.network.port.mariadb }}

View File

@ -0,0 +1,84 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-peer-finder
data:
peer-finder.py: |+
import json
import os
import urllib2
import ssl
import socket
import sys
import time
URL = ('https://kubernetes.default.svc.cluster.local/api/v1/namespaces/{namespace}'
'/endpoints/{service_name}')
TOKEN_FILE = '/var/run/secrets/kubernetes.io/serviceaccount/token'
def get_service_endpoints(service_name):
url = URL.format(namespace=os.environ['NAMESPACE'], service_name=service_name)
try:
token = file (TOKEN_FILE, 'r').read()
except KeyError:
exit("Unable to open a file with token.")
header = {'Authorization': " Bearer {}".format(token)}
req = urllib2.Request(url=url, headers=header)
ctx = create_ctx()
connection = urllib2.urlopen(req, context=ctx)
data = connection.read()
# parse to dict
json_acceptable_string = data.replace("'", "\"")
output = json.loads(json_acceptable_string)
return output
def get_ip_addresses(output):
subsets = output['subsets'][0]
if not 'addresses' in subsets:
return []
ip_addresses = [x['ip'] for x in subsets['addresses']]
my_ip = get_my_ip_address()
if my_ip in ip_addresses:
ip_addresses.remove(my_ip)
return ip_addresses
def get_my_ip_address():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.connect(('kubernetes.default.svc.cluster.local', 0))
return s.getsockname()[0]
def create_ctx():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def print_galera_cluster_address(service_name):
while True:
output = get_service_endpoints(service_name)
if len(get_ip_addresses(output)):
wsrep_cluster_address = '--wsrep_cluster_address=gcomm://{}'.format(
','.join(get_ip_addresses(output)))
print wsrep_cluster_address
break
time.sleep(5)
def main():
if len(sys.argv) != 2:
exit('peer-finder: You need to pass argument')
service_name = sys.argv[1]
print_galera_cluster_address(service_name)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,8 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-pid
data:
pid.cnf: |+
[mysqld]
pid_file=/var/lib/mysql/mariadb.pid

View File

@ -0,0 +1,46 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-replicas
data:
replicas.py: |
#!/usr/bin/env python
import json
import os
import ssl
import sys
import urllib2
URL = ('https://kubernetes.default.svc.{{ .Values.network.dns.kubernetes_domain }}/apis/extensions/v1beta1/daemonsets')
TOKEN_FILE = '/var/run/secrets/kubernetes.io/serviceaccount/token'
def create_ctx():
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
return ctx
def get_daemonsets():
url = URL.format()
try:
token = file(TOKEN_FILE, 'r').read()
except KeyError:
exit("Unable to open a file with token.")
header = {'Authorization': " Bearer {}".format(token)}
req = urllib2.Request(url=url, headers=header)
ctx = create_ctx()
response = urllib2.urlopen(req, context=ctx)
output = json.load(response)
return output
def main():
reply = get_daemonsets()
name = "mariadb"
namespace = "default" if not os.environ["NAMESPACE"] else os.environ["NAMESPACE"]
mariadb = filter(lambda d: d["metadata"]["namespace"] == namespace and d["metadata"]["name"] == name, reply["items"])
print mariadb[0]["status"]['desiredNumberScheduled']
if __name__ == "__main__":
main()

View File

@ -0,0 +1,82 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-seedsh
data:
seed.sh: |+
#!/bin/sh
set -ex
SLEEP_TIMEOUT=5
function wait_for_cluster {
# Wait for the mariadb server to be "Ready" before starting the security reset with a max timeout
TIMEOUT=120
while [[ ! -f /var/lib/mysql/mariadb.pid ]]; do
if [[ ${TIMEOUT} -gt 0 ]]; then
let TIMEOUT-=1
sleep 1
else
exit 1
fi
done
REPLICAS=$(python /tmp/replicas.py)
# We need to count seed instance here.
MINIMUM_CLUSTER_SIZE=$(( $REPLICAS + 1 ))
# wait until we have at least two more members in a cluster.
while true ; do
CLUSTER_SIZE=`mysql -uroot -p"{{ .Values.database.root_password }}" --port="{{ .Values.network.port.mariadb }}" -e'show status' | grep wsrep_cluster_size | awk ' { if($2 ~ /[0-9]/){ print $2 } else { print 0 } } '`
if [ "${CLUSTER_SIZE}" -lt ${MINIMUM_CLUSTER_SIZE} ] ; then
echo "Cluster seed not finished, waiting."
sleep ${SLEEP_TIMEOUT}
continue
fi
CLUSTER_STATUS=`mysql -uroot -p"{{ .Values.database.root_password }}" --port="{{ .Values.network.port.mariadb }}" -e'show status' | grep wsrep_local_state_comment | awk ' { print $2 } '`
if [ "${CLUSTER_STATUS}" != "Synced" ] ; then
echo "Cluster not synced, waiting."
sleep ${SLEEP_TIMEOUT}
continue
fi
# Count number of endpoint separators.
ENDPOINTS_CNT=`python /tmp/peer-finder.py mariadb | grep -o ',' | wc -l`
# TODO(tomasz.paszkowski): Fix a corner case when only one endpoint is on the list.
# Add +1 for seed node and +1 as first item does not have a separator
ENDPOINTS_CNT=$(($ENDPOINTS_CNT+2))
if [ "${ENDPOINTS_CNT}" != "${CLUSTER_SIZE}" ] ; then
echo "Cluster not synced, waiting."
sleep ${SLEEP_TIMEOUT}
continue
fi
echo "Cluster ready, exiting seed."
kill -- -$$
break
done
}
# With the DaemonSet implementation, there may be a difference
# in the number of replicas and actual number of nodes matching
# mariadb node selector label. Problem will be solved when
# the implementation will be switched to Deployment
# (using anti-affinity feature).
REPLICAS=$(python /tmp/replicas.py)
if [ "$REPLICAS" -eq 1 ] ; then
echo "Requested to build one-instance MariaDB cluster. There is no need to run seed. Exiting."
exit 0
elif [ "$REPLICAS" -eq 2 ] ; then
echo "2-instance cluster is not a valid MariaDB configuration."
exit 1
fi
bash /tmp/bootstrap-db.sh
mysqld_safe --defaults-file=/etc/my.cnf \
--console \
--wsrep-new-cluster \
--wsrep_cluster_address='gcomm://' &
wait_for_cluster
exit 0

View File

@ -0,0 +1,37 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-startsh
data:
start.sh: |+
#!/bin/bash
set -ex
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
sudo chown mysql: /var/lib/mysql
REPLICAS=$(python /tmp/replicas.py)
INIT_MARKER="/var/lib/mysql/init_done"
# Remove mariadb.pid if exists
if [[ -f /var/lib/mysql/mariadb.pid ]]; then
if [[ `pgrep -c $(cat /var/lib/mysql/mariadb.pid)` -eq 0 ]]; then
rm -vf /var/lib/mysql/mariadb.pid
fi
fi
if [ "$REPLICAS" -eq 1 ] ; then
if [[ ! -f ${INIT_MARKER} ]]; then
cd /var/lib/mysql
echo "Creating one-instance MariaDB."
bash /tmp/bootstrap-db.sh
touch ${INIT_MARKER}
fi
exec mysqld_safe --defaults-file=/etc/my.cnf \
--console \
--wsrep-new-cluster \
--wsrep_cluster_address='gcomm://'
else
export WSREP_OPTIONS=`python /tmp/peer-finder.py mariadb`
exec mysqld --defaults-file=/etc/my.cnf --console $WSREP_OPTIONS
fi

View File

@ -0,0 +1,53 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-tuning
data:
tuning.cnf: |+
[mysqld]
user=mysql
max_allowed_packet=256M
open_files_limit=10240
max_connections=8192
max-connect-errors=1000000
## Generally, it is unwise to set the query cache to be larger than 64-128M
## as the costs associated with maintaining the cache outweigh the performance
## gains.
## The query cache is a well known bottleneck that can be seen even when
## concurrency is moderate. The best option is to disable it from day 1
## by setting query_cache_size=0 (now the default on MySQL 5.6)
## and to use other ways to speed up read queries: good indexing, adding
## replicas to spread the read load or using an external cache.
query_cache_size =0
query_cache_type=0
sync_binlog=0
thread_cache_size=16
table_open_cache=2048
table_definition_cache=1024
#
# InnoDB
#
# The buffer pool is where data and indexes are cached: having it as large as possible
# will ensure you use memory and not disks for most read operations.
# Typical values are 50..75% of available RAM.
# TODO(tomasz.paszkowski): This needs to by dynamic based on avaliable RAM.
innodb_buffer_pool_size=4096M
innodb_log_file_size=2000M
innodb_flush_method=O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_old_blocks_time=1000
innodb_autoinc_lock_mode=2
innodb_doublewrite=0
innodb_file_format=Barracuda
innodb_file_per_table=1
innodb_io_capacity=500
innodb_locks_unsafe_for_binlog=1
innodb_read_io_threads=8
innodb_write_io_threads=8
[mysqldump]
max-allowed-packet=16M

View File

@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: mariadb-wsrep
data:
wsrep.cnf: |+
[mysqld]
wsrep_cluster_name="{{ .Values.database.cluster_name }}"
wsrep_provider=/usr/lib/galera/libgalera_smm.so
wsrep_provider_options="gcache.size=512M"
wsrep_slave_threads=12
wsrep_sst_auth=root:{{ .Values.database.root_password }}
wsrep_sst_method=xtrabackup-v2
wsrep_node_name={{ .Values.database.node_name }}
wsrep_node_address={{ .Values.network.ip_address }}:{{ .Values.network.port.wsrep }}

14
mariadb/values.yaml Normal file
View File

@ -0,0 +1,14 @@
deployment:
image: quay.io/stackanetes/stackanetes-mariadb:newton
control_node_label: openstack-control-plane
network:
port:
wsrep: 4567
mariadb: 3306
dns:
kubernetes_domain: cluster.local
ip_address: "{{ .IP }}"
database:
root_password: password
cluster_name: mariadb
node_name: master