Example: MySQL Database
MySQL Database
Install and configure MySQL with application databases, user privileges, automated backups, and performance tuning.
Directory Structure
cookbooks/
mysql/
default.rb
templates/
mysqld.cnf.erb
mysql-backup.sh.erb
my.cnf.erb
nodes/
db01.json
Node Attributes
{
"mysql": {
"version": "8.0",
"port": 3306,
"bind_address": "0.0.0.0",
"data_dir": "/var/lib/mysql",
"innodb_buffer_pool_size": "1G",
"max_connections": 200,
"query_cache_size": "64M",
"slow_query_log": true,
"slow_query_time": 2,
"db_name": "myapp_production",
"db_user": "myapp",
"db_password": "s3cure-db-pass",
"db_host": "10.0.%",
"root_password": "r00t-s3cret",
"backup_dir": "/var/backups/mysql",
"backup_retention_days": 7
}
}
Recipe
# cookbooks/mysql/default.rb
mysql = node['mysql']
package 'mysql-server' do
action :install
end
group 'mysql' do
gid 3306
end
user 'mysql' do
uid 3306
gid 3306
home mysql['data_dir']
shell '/usr/sbin/nologin'
system_user true
end
directory mysql['data_dir'] do
owner 'mysql'
group 'mysql'
mode '0750'
end
directory mysql['backup_dir'] do
owner 'mysql'
group 'mysql'
mode '0750'
end
directory '/var/log/mysql' do
owner 'mysql'
group 'mysql'
mode '0750'
end
template '/etc/mysql/mysql.conf.d/mysqld.cnf' do
source 'templates/mysqld.cnf.erb'
owner 'root'
group 'root'
mode '0644'
variables(
port: mysql['port'],
bind_address: mysql['bind_address'],
data_dir: mysql['data_dir'],
innodb_buffer_pool_size: mysql['innodb_buffer_pool_size'],
max_connections: mysql['max_connections'],
slow_query_log: mysql['slow_query_log'],
slow_query_time: mysql['slow_query_time']
)
notifies :restart, 'service[mysql]'
end
# Create application database and user
execute "create-db-#{mysql['db_name']}" do
command "mysql -u root -e \"CREATE DATABASE IF NOT EXISTS #{mysql['db_name']} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;\""
not_if "mysql -u root -e \"SHOW DATABASES LIKE '#{mysql['db_name']}'\" | grep -q #{mysql['db_name']}"
end
execute "create-user-#{mysql['db_user']}" do
command "mysql -u root -e \"CREATE USER IF NOT EXISTS '#{mysql['db_user']}'@'#{mysql['db_host']}' IDENTIFIED BY '#{mysql['db_password']}'; GRANT ALL PRIVILEGES ON #{mysql['db_name']}.* TO '#{mysql['db_user']}'@'#{mysql['db_host']}'; FLUSH PRIVILEGES;\""
not_if "mysql -u root -e \"SELECT User FROM mysql.user WHERE User='#{mysql['db_user']}'\" | grep -q #{mysql['db_user']}"
end
# Client config for root (passwordless local access)
template '/root/.my.cnf' do
source 'templates/my.cnf.erb'
owner 'root'
group 'root'
mode '0600'
variables(
root_password: mysql['root_password']
)
end
# Backup script
template '/usr/local/bin/mysql-backup.sh' do
source 'templates/mysql-backup.sh.erb'
owner 'root'
group 'root'
mode '0755'
variables(
db_name: mysql['db_name'],
backup_dir: mysql['backup_dir'],
retention_days: mysql['backup_retention_days']
)
end
service 'mysql' do
action [:enable, :start]
end
Templates
mysqld.cnf.erb
[mysqld]
port = <%= @port %>
bind-address = <%= @bind_address %>
datadir = <%= @data_dir %>
innodb_buffer_pool_size = <%= @innodb_buffer_pool_size %>
max_connections = <%= @max_connections %>
<% if @slow_query_log %>
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = <%= @slow_query_time %>
<% end %>
log_error = /var/log/mysql/error.log
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
my.cnf.erb
[client]
user = root
password = <%= @root_password %>
mysql-backup.sh.erb
#!/bin/bash
BACKUP_DIR="<%= @backup_dir %>"
DB_NAME="<%= @db_name %>"
RETENTION=<%= @retention_days %>
mysqldump --single-transaction "$DB_NAME" | gzip > "$BACKUP_DIR/${DB_NAME}_$(date +%Y%m%d_%H%M%S).sql.gz"
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +"$RETENTION" -delete
Running
itamae ssh -j nodes/db01.json -h db01.example.com cookbooks/mysql/default.rb