mirror of
https://github.com/BagelHole/DevOps-Security-Agent-Skills.git
synced 2026-08-22 12:49:53 +02:00
V2
This commit is contained in:
@@ -9,70 +9,365 @@ metadata:
|
||||
|
||||
# MySQL / MariaDB
|
||||
|
||||
Administer MySQL and MariaDB databases.
|
||||
Administer, optimize, and secure MySQL and MariaDB databases in development and production environments.
|
||||
|
||||
## Installation & Setup
|
||||
## When to Use
|
||||
|
||||
- You need a mature, widely supported relational database.
|
||||
- Your stack depends on MySQL-specific features or compatibility (WordPress, Magento, many PHP frameworks).
|
||||
- You are setting up source-replica replication for read scaling.
|
||||
- You want to tune InnoDB for high-throughput transactional workloads.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Linux server (Debian/Ubuntu or RHEL-based) or Docker.
|
||||
- Root or sudo access for package installation.
|
||||
- Familiarity with SQL fundamentals.
|
||||
|
||||
## Installation and Setup
|
||||
|
||||
```bash
|
||||
# Debian / Ubuntu — MySQL 8
|
||||
sudo apt update
|
||||
sudo apt install -y mysql-server
|
||||
|
||||
# RHEL / Amazon Linux
|
||||
sudo dnf install -y mysql-server
|
||||
sudo systemctl enable --now mysqld
|
||||
|
||||
# Run the secure installation wizard
|
||||
sudo mysql_secure_installation
|
||||
# Prompts: set root password, remove anonymous users, disable remote root, remove test db
|
||||
|
||||
# Verify
|
||||
mysql --version
|
||||
sudo systemctl status mysql
|
||||
```
|
||||
|
||||
## Initial User and Database Setup
|
||||
|
||||
```bash
|
||||
sudo mysql -u root -p
|
||||
```
|
||||
|
||||
```sql
|
||||
-- Create a database
|
||||
CREATE DATABASE mydb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
-- Create an application user with strong auth
|
||||
CREATE USER 'myapp'@'%' IDENTIFIED BY 'strong_password_here';
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'myapp'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
-- Verify
|
||||
SHOW GRANTS FOR 'myapp'@'%';
|
||||
```
|
||||
|
||||
## mysql CLI Reference
|
||||
|
||||
```bash
|
||||
# Connect
|
||||
mysql -u myapp -p -h 127.0.0.1 mydb
|
||||
|
||||
# Execute a single statement
|
||||
mysql -u myapp -p -e "SELECT COUNT(*) FROM orders;" mydb
|
||||
|
||||
# Import a SQL file
|
||||
mysql -u myapp -p mydb < schema.sql
|
||||
|
||||
# Export query results to CSV
|
||||
mysql -u myapp -p -e "SELECT * FROM users" mydb \
|
||||
| tr '\t' ',' > users.csv
|
||||
```
|
||||
|
||||
```
|
||||
-- Inside the mysql shell
|
||||
SHOW DATABASES;
|
||||
USE mydb;
|
||||
SHOW TABLES;
|
||||
DESCRIBE users;
|
||||
SHOW CREATE TABLE users\G
|
||||
SHOW PROCESSLIST;
|
||||
SHOW ENGINE INNODB STATUS\G
|
||||
```
|
||||
|
||||
## Configuration Tuning
|
||||
|
||||
Edit `/etc/mysql/mysql.conf.d/mysqld.cnf` (or `/etc/my.cnf` on RHEL).
|
||||
|
||||
```ini
|
||||
[mysqld]
|
||||
# -- Networking --
|
||||
bind-address = 0.0.0.0
|
||||
max_connections = 300
|
||||
wait_timeout = 600
|
||||
interactive_timeout = 600
|
||||
|
||||
# -- InnoDB (most impactful settings) --
|
||||
innodb_buffer_pool_size = 4G # ~70% of RAM on a dedicated server
|
||||
innodb_buffer_pool_instances = 4 # 1 per GB of pool (up to 64)
|
||||
innodb_log_file_size = 1G
|
||||
innodb_flush_log_at_trx_commit = 1 # 1 = ACID; 2 = faster, slight risk
|
||||
innodb_flush_method = O_DIRECT # avoids double buffering on Linux
|
||||
innodb_io_capacity = 2000 # raise for SSD
|
||||
innodb_io_capacity_max = 4000
|
||||
|
||||
# -- Query cache (disabled in MySQL 8, use ProxySQL or app cache) --
|
||||
# query_cache_type = 0
|
||||
|
||||
# -- Logging --
|
||||
slow_query_log = 1
|
||||
slow_query_log_file = /var/log/mysql/slow.log
|
||||
long_query_time = 1
|
||||
log_error = /var/log/mysql/error.log
|
||||
|
||||
# -- Binary log (required for replication) --
|
||||
server-id = 1
|
||||
log_bin = /var/log/mysql/mysql-bin
|
||||
binlog_expire_logs_seconds = 604800 # 7 days
|
||||
sync_binlog = 1
|
||||
|
||||
# -- Character set --
|
||||
character-set-server = utf8mb4
|
||||
collation-server = utf8mb4_unicode_ci
|
||||
```
|
||||
|
||||
```bash
|
||||
# Apply changes
|
||||
sudo systemctl restart mysql
|
||||
|
||||
# Verify a setting at runtime
|
||||
mysql -u root -p -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
|
||||
```
|
||||
|
||||
## Backup and Restore
|
||||
|
||||
### Logical Backups with mysqldump
|
||||
|
||||
```bash
|
||||
# Single database
|
||||
mysqldump -u root -p --single-transaction --routines --triggers \
|
||||
mydb > /backups/mydb_$(date +%F).sql
|
||||
|
||||
# All databases
|
||||
mysqldump -u root -p --all-databases --single-transaction \
|
||||
> /backups/all_$(date +%F).sql
|
||||
|
||||
# Compressed backup
|
||||
mysqldump -u root -p --single-transaction mydb \
|
||||
| gzip > /backups/mydb_$(date +%F).sql.gz
|
||||
|
||||
# Restore
|
||||
mysql -u root -p mydb < /backups/mydb_2025-01-15.sql
|
||||
|
||||
# Restore compressed
|
||||
gunzip < /backups/mydb_2025-01-15.sql.gz | mysql -u root -p mydb
|
||||
```
|
||||
|
||||
### Physical Backups with Percona XtraBackup
|
||||
|
||||
```bash
|
||||
# Install
|
||||
apt install mysql-server
|
||||
sudo apt install -y percona-xtrabackup-80
|
||||
|
||||
# Secure installation
|
||||
mysql_secure_installation
|
||||
# Full backup
|
||||
xtrabackup --backup --user=root --password=secret \
|
||||
--target-dir=/backups/full_$(date +%F)
|
||||
|
||||
# Access
|
||||
mysql -u root -p
|
||||
# Prepare the backup (apply redo logs)
|
||||
xtrabackup --prepare --target-dir=/backups/full_2025-01-15
|
||||
|
||||
# Create database and user
|
||||
CREATE DATABASE mydb;
|
||||
CREATE USER 'myapp'@'%' IDENTIFIED BY 'secret';
|
||||
GRANT ALL PRIVILEGES ON mydb.* TO 'myapp'@'%';
|
||||
FLUSH PRIVILEGES;
|
||||
# Restore (stop MySQL first)
|
||||
sudo systemctl stop mysql
|
||||
sudo rm -rf /var/lib/mysql/*
|
||||
xtrabackup --move-back --target-dir=/backups/full_2025-01-15
|
||||
sudo chown -R mysql:mysql /var/lib/mysql
|
||||
sudo systemctl start mysql
|
||||
```
|
||||
|
||||
## Configuration
|
||||
### Incremental Backup with XtraBackup
|
||||
|
||||
```bash
|
||||
# Incremental based on the full backup
|
||||
xtrabackup --backup --user=root --password=secret \
|
||||
--target-dir=/backups/inc_$(date +%F) \
|
||||
--incremental-basedir=/backups/full_2025-01-15
|
||||
|
||||
# Prepare: apply full, then incremental
|
||||
xtrabackup --prepare --apply-log-only --target-dir=/backups/full_2025-01-15
|
||||
xtrabackup --prepare --target-dir=/backups/full_2025-01-15 \
|
||||
--incremental-dir=/backups/inc_2025-01-16
|
||||
```
|
||||
|
||||
## Source-Replica Replication
|
||||
|
||||
### Source (Primary)
|
||||
|
||||
```ini
|
||||
# /etc/mysql/mysql.conf.d/mysqld.cnf
|
||||
[mysqld]
|
||||
innodb_buffer_pool_size = 1G
|
||||
max_connections = 200
|
||||
slow_query_log = 1
|
||||
long_query_time = 2
|
||||
server-id = 1
|
||||
log_bin = /var/log/mysql/mysql-bin
|
||||
binlog_format = ROW
|
||||
```
|
||||
|
||||
## Backup & Restore
|
||||
```sql
|
||||
-- Create replication user
|
||||
CREATE USER 'replicator'@'10.0.0.%' IDENTIFIED BY 'repl_secret';
|
||||
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'10.0.0.%';
|
||||
FLUSH PRIVILEGES;
|
||||
|
||||
```bash
|
||||
# Backup
|
||||
mysqldump -u root -p mydb > backup.sql
|
||||
mysqldump -u root -p --all-databases > full_backup.sql
|
||||
|
||||
# Restore
|
||||
mysql -u root -p mydb < backup.sql
|
||||
-- Get current binary log position
|
||||
SHOW MASTER STATUS\G
|
||||
```
|
||||
|
||||
## Replication
|
||||
### Replica
|
||||
|
||||
```bash
|
||||
# Primary
|
||||
```ini
|
||||
# /etc/mysql/mysql.conf.d/mysqld.cnf
|
||||
[mysqld]
|
||||
server-id = 1
|
||||
log_bin = mysql-bin
|
||||
|
||||
# Replica
|
||||
CHANGE MASTER TO
|
||||
MASTER_HOST='primary',
|
||||
MASTER_USER='replicator',
|
||||
MASTER_PASSWORD='secret',
|
||||
MASTER_LOG_FILE='mysql-bin.000001',
|
||||
MASTER_LOG_POS=0;
|
||||
START SLAVE;
|
||||
server-id = 2
|
||||
relay_log = /var/log/mysql/relay-bin
|
||||
read_only = ON
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
```sql
|
||||
-- Point replica to source (use SHOW MASTER STATUS values)
|
||||
CHANGE REPLICATION SOURCE TO
|
||||
SOURCE_HOST = '10.0.0.1',
|
||||
SOURCE_USER = 'replicator',
|
||||
SOURCE_PASSWORD = 'repl_secret',
|
||||
SOURCE_LOG_FILE = 'mysql-bin.000003',
|
||||
SOURCE_LOG_POS = 154;
|
||||
|
||||
- Enable slow query logging
|
||||
- Use InnoDB storage engine
|
||||
- Regular backups with mysqldump
|
||||
- Monitor with SHOW PROCESSLIST
|
||||
START REPLICA;
|
||||
|
||||
-- Verify
|
||||
SHOW REPLICA STATUS\G
|
||||
-- Check: Replica_IO_Running = Yes, Replica_SQL_Running = Yes, Seconds_Behind_Source = 0
|
||||
```
|
||||
|
||||
## Monitoring Queries
|
||||
|
||||
```sql
|
||||
-- Connection statistics
|
||||
SHOW STATUS LIKE 'Threads_connected';
|
||||
SHOW STATUS LIKE 'Max_used_connections';
|
||||
|
||||
-- InnoDB buffer pool hit ratio (should be > 99%)
|
||||
SELECT
|
||||
ROUND(100 - (
|
||||
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_reads') /
|
||||
(SELECT VARIABLE_VALUE FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Innodb_buffer_pool_read_requests')
|
||||
) * 100, 2) AS buffer_pool_hit_pct;
|
||||
|
||||
-- Top 10 slow queries (requires performance_schema)
|
||||
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT / 1e12 AS avg_sec
|
||||
FROM performance_schema.events_statements_summary_by_digest
|
||||
ORDER BY AVG_TIMER_WAIT DESC
|
||||
LIMIT 10;
|
||||
|
||||
-- Table sizes
|
||||
SELECT table_name,
|
||||
ROUND(data_length / 1024 / 1024, 2) AS data_mb,
|
||||
ROUND(index_length / 1024 / 1024, 2) AS index_mb,
|
||||
table_rows
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'mydb'
|
||||
ORDER BY data_length DESC;
|
||||
|
||||
-- Check replication lag
|
||||
SHOW REPLICA STATUS\G
|
||||
-- Look at Seconds_Behind_Source
|
||||
```
|
||||
|
||||
## Docker Compose Setup
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "3306:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
MYSQL_DATABASE: mydb
|
||||
MYSQL_USER: myapp
|
||||
MYSQL_PASSWORD: secret
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
|
||||
command: >
|
||||
--innodb-buffer-pool-size=512M
|
||||
--max-connections=200
|
||||
--slow-query-log=ON
|
||||
--long-query-time=1
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_unicode_ci
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpass"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
phpmyadmin:
|
||||
image: phpmyadmin:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8080:80"
|
||||
environment:
|
||||
PMA_HOST: mysql
|
||||
PMA_USER: root
|
||||
PMA_PASSWORD: rootpass
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
```
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
mysql -h 127.0.0.1 -u myapp -psecret mydb
|
||||
```
|
||||
|
||||
## Maintenance Tasks
|
||||
|
||||
```bash
|
||||
# Optimize a fragmented table (locks the table briefly)
|
||||
mysql -u root -p -e "OPTIMIZE TABLE mydb.orders;"
|
||||
|
||||
# Analyze tables to update statistics
|
||||
mysql -u root -p -e "ANALYZE TABLE mydb.orders;"
|
||||
|
||||
# Check and repair a table
|
||||
mysql -u root -p -e "CHECK TABLE mydb.orders;"
|
||||
mysql -u root -p -e "REPAIR TABLE mydb.orders;"
|
||||
|
||||
# Rotate slow query log
|
||||
sudo mv /var/log/mysql/slow.log /var/log/mysql/slow.log.old
|
||||
mysqladmin -u root -p flush-logs
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Likely Cause | Fix |
|
||||
|---|---|---|
|
||||
| `Too many connections` | Connection limit exceeded | Increase `max_connections`; use connection pooling (ProxySQL) |
|
||||
| Slow queries across the board | `innodb_buffer_pool_size` too small | Set to ~70% of available RAM and restart |
|
||||
| Replication stopped (`SQL_Running: No`) | Duplicate key or schema mismatch on replica | Check `SHOW REPLICA STATUS\G` error; skip or fix the row |
|
||||
| `Table is full` | Disk space exhausted or table limit hit | Free disk space; check `innodb_data_file_path` autoextend |
|
||||
| `Lock wait timeout exceeded` | Long-running transaction holding row locks | Identify with `SHOW ENGINE INNODB STATUS`; kill the blocking query |
|
||||
| High IOPS / disk usage | Redo log too small causing frequent flushes | Increase `innodb_log_file_size` (requires restart) |
|
||||
|
||||
## Related Skills
|
||||
|
||||
- [postgresql](../postgresql/) - Alternative relational database
|
||||
- [database-backups](../database-backups/) - Automated backup strategies
|
||||
- [redis](../redis/) - Caching layer to reduce database load
|
||||
- [planetscale](../planetscale/) - Managed MySQL-compatible with branching
|
||||
|
||||
Reference in New Issue
Block a user