diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5bd64dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.terraform/ +*.tfstate +*.tfstate.lock.info +*.tfstate.backup +crash.log +custom-plugins/ +*.lock.hcl +*.auto.* +env.sh \ No newline at end of file diff --git a/mysql/common/common_variables.tf b/mysql/common/common_variables.tf new file mode 100644 index 0000000..4fe9130 --- /dev/null +++ b/mysql/common/common_variables.tf @@ -0,0 +1,77 @@ +variable "instance_type" { + type = string + description = "MySQL instance type" + default = "ecs.t1.c1m2" + validation { + condition = var.instance_type != "" && contains([ + "ecs.t1.c1m2", + "ecs.t1.c2m4", + "ecs.t1.c4m8", + "ecs.t1.c12m24", + "ecs.t1.c32m64", + "ecs.t1.c24m48", + "ecs.t1.c8m16", + "ecs.t1.c16m32", + "ecs.g1.c16m120", + "ecs.g1.c32m240", + "ecs.c1.c1m2", + "ecs.c1.c2m4", + "ecs.c1.c4m8", + "ecs.c1.c8m16", + "ecs.c1.c16m32", + "ecs.c1.c24m48", + "ecs.c1.c12m24", + "ecs.c1.c32m64", + ], var.instance_type) + error_message = "instance_type parameter must be one of the allowed instance types" + } +} + +variable "instance_system_disk_size" { + type = number + description = "System disk size in GiB" + default = 20 + + validation { + condition = var.instance_system_disk_size > 0 + error_message = "instance_system_disk_size parameter must be a positive integer" + } +} + +variable "mysql_username" { + type = string + description = "MySQL username" + default = "admin" + + validation { + condition = length(var.mysql_username) >= 1 && length(var.mysql_username) <= 32 + error_message = "mysql_username parameter must be between 1 and 32 characters long" + } +} + +variable "mysql_password" { + type = string + description = "MySQL password" + sensitive = true + + validation { + condition = length(var.mysql_password) >= 8 + error_message = "mysql_password parameter must be at least 8 characters long" + } + + validation { + condition = can(regex("[a-z]", var.mysql_password)) && can(regex("[A-Z]", var.mysql_password)) && can(regex("[0-9]", var.mysql_password)) && can(regex("[!-/:-@\\[-`{-~]", var.mysql_password)) + error_message = "mysql_password parameter must contain at least one lowercase letter, one uppercase letter, one digit, and one special character" + } +} + +variable "mysql_db_name" { + type = string + description = "Initial MySQL database name (optional)" + default = "" + + validation { + condition = var.mysql_db_name == "" || length(var.mysql_db_name) >= 1 && length(var.mysql_db_name) <= 64 && can(regex("^[a-zA-Z0-9_]*$", var.mysql_db_name)) && !contains(["mysql", "information_schema", "performance_schema", "sys"], var.mysql_db_name) + error_message = "mysql_db_name must be 1-64 chars, only alphanumeric/underscore, and not a reserved name (mysql, information_schema, performance_schema, sys)" + } +} diff --git a/mysql/common/common_versions.tf b/mysql/common/common_versions.tf new file mode 100644 index 0000000..5ad6e7b --- /dev/null +++ b/mysql/common/common_versions.tf @@ -0,0 +1,18 @@ +terraform { + required_version = "> 0.12.0" + + required_providers { + qiniu = { + source = "hashicorp/qiniu" + version = "~> 1.0.0" + } + random = { + source = "hashicorp/random" + version = "~> 3.0" + } + } +} + +provider "qiniu" {} + +provider "random" {} diff --git a/mysql/common/image_data.tf b/mysql/common/image_data.tf new file mode 100644 index 0000000..3f6238b --- /dev/null +++ b/mysql/common/image_data.tf @@ -0,0 +1,12 @@ +data "qiniu_compute_images" "available_official_images" { + type = "Official" + state = "Available" +} + +locals { + // 选用的系统镜像ID + ubuntu_image_id = one([ + for item in data.qiniu_compute_images.available_official_images.items : item + if item.os_distribution == "Ubuntu" && item.os_version == "24.04 LTS" + ]).id +} diff --git a/mysql/replication/data.tf b/mysql/replication/data.tf new file mode 100644 index 0000000..490d112 --- /dev/null +++ b/mysql/replication/data.tf @@ -0,0 +1,29 @@ +# 为 MySQL 复制用户生成随机密码 +resource "random_password" "replication_password" { + length = 16 + special = true + lower = true + upper = true + numeric = true +} + +locals { + // MySQL 复制用户名称 + replication_username = var.mysql_replication_username + + // 随机生成的 MySQL 复制用户密码 + replication_password = random_password.replication_password.result +} + +# 用于生成资源后缀 +resource "random_string" "random_suffix" { + length = 6 + upper = false + lower = true + special = false +} + +locals { + // 资源组随机后缀 + cluster_suffix = random_string.random_suffix.result +} diff --git a/mysql/replication/image_data.tf b/mysql/replication/image_data.tf new file mode 120000 index 0000000..cb4b77d --- /dev/null +++ b/mysql/replication/image_data.tf @@ -0,0 +1 @@ +../common/image_data.tf \ No newline at end of file diff --git a/mysql/replication/main.tf b/mysql/replication/main.tf new file mode 100644 index 0000000..e478889 --- /dev/null +++ b/mysql/replication/main.tf @@ -0,0 +1,50 @@ +# MySQL Replication Cluster Configuration + +# 创建置放组 +resource "qiniu_compute_placement_group" "mysql_pg" { + name = format("mysql-repl-%s", local.cluster_suffix) + description = format("Placement group for MySQL replication cluster %s", local.cluster_suffix) + strategy = "Spread" +} + +# 创建 MySQL 主库 +resource "qiniu_compute_instance" "mysql_primary_node" { + instance_type = var.instance_type + placement_group_id = qiniu_compute_placement_group.mysql_pg.id + name = format("mysql-primary-%s", local.cluster_suffix) + description = format("Primary node for MySQL replication cluster %s", local.cluster_suffix) + image_id = local.ubuntu_image_id + system_disk_size = var.instance_system_disk_size + + user_data = base64encode(templatefile("${path.module}/mysql_master.sh", { + mysql_server_id = "1" + mysql_admin_username = var.mysql_username + mysql_admin_password = var.mysql_password + mysql_replication_username = local.replication_username + mysql_replication_password = local.replication_password + mysql_db_name = var.mysql_db_name + })) +} + + +# 创建 MySQL 从库节点 +resource "qiniu_compute_instance" "mysql_replication_nodes" { + depends_on = [qiniu_compute_instance.mysql_primary_node] + + count = var.mysql_replica_count + instance_type = var.instance_type + placement_group_id = qiniu_compute_placement_group.mysql_pg.id + name = format("mysql-repl-%02d-%s", count.index + 1, local.cluster_suffix) + description = format("Replica node %02d for MySQL replication cluster %s", count.index + 1, local.cluster_suffix) + image_id = local.ubuntu_image_id + system_disk_size = var.instance_system_disk_size + + user_data = base64encode(templatefile("${path.module}/mysql_slave.sh", { + mysql_master_ip = qiniu_compute_instance.mysql_primary_node.private_ip_addresses[0].ipv4 + mysql_server_id = tostring(count.index + 2) // 从库ID从2开始递增 + mysql_replication_username = local.replication_username + mysql_replication_password = local.replication_password + })) +} + + diff --git a/mysql/replication/mysql_master.sh b/mysql/replication/mysql_master.sh new file mode 100644 index 0000000..e6826ba --- /dev/null +++ b/mysql/replication/mysql_master.sh @@ -0,0 +1,58 @@ +#!/bin/bash +set -e + +# Install MySQL if not already installed +echo "Checking for MySQL installation..." +if ! command -v mysql &> /dev/null; then + echo "MySQL not found, installing..." + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-client-8.0 mysql-server-8.0 mysql-router mysql-shell +fi + +echo "This is the primary node." + +# 允许外部IP访问 +sed -i 's/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf + +# 确保删除旧的server uuid配置文件,防止uuid冲突 +rm -f /var/lib/mysql/auto.cnf + +# 配置主从复制 +tee /etc/mysql/mysql.conf.d/replication.cnf >/dev/null < /dev/null; then + echo "MySQL not found, installing..." + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-client-8.0 mysql-server-8.0 mysql-router mysql-shell +fi + +echo "This is a replica node." + +# 允许外部IP访问 +sed -i 's/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf + +# 确保删除旧的server uuid配置文件,防止uuid冲突 +rm -f /var/lib/mysql/auto.cnf + +# 配置主从复制 +tee /etc/mysql/mysql.conf.d/replication.cnf >/dev/null <= 1 && var.mysql_replica_count <= 2 + error_message = "mysql_replica_count must be between 1 and 2" + } +} + +variable "mysql_replication_username" { + type = string + description = "MySQL replication username" + default = "replication" + + validation { + condition = length(var.mysql_replication_username) >= 1 && length(var.mysql_replication_username) <= 32 + error_message = "mysql_replication_username parameter must be between 1 and 32 characters long" + } +} diff --git a/mysql/replication/variables.tf b/mysql/replication/variables.tf new file mode 120000 index 0000000..5884570 --- /dev/null +++ b/mysql/replication/variables.tf @@ -0,0 +1 @@ +../common/common_variables.tf \ No newline at end of file diff --git a/mysql/replication/versions.tf b/mysql/replication/versions.tf new file mode 120000 index 0000000..1b092e6 --- /dev/null +++ b/mysql/replication/versions.tf @@ -0,0 +1 @@ +../common/common_versions.tf \ No newline at end of file diff --git a/mysql/standalone/data.tf b/mysql/standalone/data.tf new file mode 100644 index 0000000..7f90619 --- /dev/null +++ b/mysql/standalone/data.tf @@ -0,0 +1,11 @@ +# 生成资源后缀,避免命名冲突 +resource "random_string" "resource_suffix" { + length = 6 + upper = false + lower = true + special = false +} + +locals { + standalone_suffix = random_string.resource_suffix.result +} diff --git a/mysql/standalone/image_data.tf b/mysql/standalone/image_data.tf new file mode 120000 index 0000000..cb4b77d --- /dev/null +++ b/mysql/standalone/image_data.tf @@ -0,0 +1 @@ +../common/image_data.tf \ No newline at end of file diff --git a/mysql/standalone/main.tf b/mysql/standalone/main.tf new file mode 100644 index 0000000..aad8b99 --- /dev/null +++ b/mysql/standalone/main.tf @@ -0,0 +1,12 @@ +resource "qiniu_compute_instance" "mysql_primary_node" { + instance_type = var.instance_type // 虚拟机实例规格 + name = format("mysql-standalone-%s", local.standalone_suffix) + description = format("Standalone MySQL node %s", local.standalone_suffix) + image_id = local.ubuntu_image_id // 预设的MysSQL系统镜像ID + system_disk_size = var.instance_system_disk_size // 系统盘大小,单位是GiB + user_data = base64encode(templatefile("${path.module}/mysql_standalone.sh", { + mysql_username = var.mysql_username, + mysql_password = var.mysql_password, + mysql_db_name = var.mysql_db_name, + })) +} diff --git a/mysql/standalone/mysql_standalone.sh b/mysql/standalone/mysql_standalone.sh new file mode 100644 index 0000000..f8626a0 --- /dev/null +++ b/mysql/standalone/mysql_standalone.sh @@ -0,0 +1,43 @@ +#!/bin/bash +set -e + +# Install MySQL if not already installed + +echo "Checking for MySQL installation..." + +if ! command -v mysql &> /dev/null; then + echo "MySQL not found, installing..." + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-client-8.0 mysql-server-8.0 mysql-router mysql-shell +fi + +echo "Setting up MySQL standalone instance..." + +# 允许外部IP访问 +sed -i 's/^bind-address\s*=\s*127.0.0.1/bind-address = 0.0.0.0/' /etc/mysql/mysql.conf.d/mysqld.cnf + +# 确保删除旧的server uuid配置文件,防止uuid冲突 +rm -f /var/lib/mysql/auto.cnf + +# 重启 MySQL 服务 +systemctl restart mysql + +# 等待 MySQL 服务重启完成 +while ! mysqladmin ping --silent; do sleep 1; done + +# 配置基础用户 +mysql -uroot <