From af53cf0d3a2e8612d61bf105042dc8f598cd728e Mon Sep 17 00:00:00 2001 From: Dingo Date: Tue, 3 Mar 2026 11:34:42 +0700 Subject: [PATCH] Initial commit: Terraform plan for Singapore production ACK cluster - Creates VPC with 3 vswitches across availability zones - Creates security group with ports for all production services - Creates ACK managed Kubernetes cluster with 10 worker nodes - Auto-scaling up to 20 worker nodes - Creates SLB for load balancing - Includes deploy.sh script to apply cluster-all.yaml resources --- .gitignore | 15 +++ deploy.sh | 130 +++++++++++++++++++++++ main.tf | 295 +++++++++++++++++++++++++++++++++++++++++++++++++++ variables.tf | 93 ++++++++++++++++ 4 files changed, 533 insertions(+) create mode 100644 .gitignore create mode 100755 deploy.sh create mode 100644 main.tf create mode 100644 variables.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b9ce26 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Terraform +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 0000000..9b2de49 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,130 @@ +#!/bin/bash +# Lingble Production Cluster Deployment Script +# This script deploys services from cluster-all.yaml to the new Singapore ACK cluster + +set -e + +CLUSTER_ALL_FILE="${CLUSTER_ALL_FILE:-/Users/dingo/cluster-all.yaml}" +TEMP_DIR="/tmp/lingble-deploy-$$" + +echo "=== Lingble Production Cluster Deployment ===" +echo "Cluster All File: $CLUSTER_ALL_FILE" +echo "Target Region: Singapore (ap-southeast-1)" + +# Check if kubeconfig exists +if [ ! -f "./kubeconfig" ]; then + echo "Error: kubeconfig file not found. Run 'terraform apply' first." + exit 1 +fi + +# Check if cluster-all.yaml exists +if [ ! -f "$CLUSTER_ALL_FILE" ]; then + echo "Error: cluster-all.yaml not found at $CLUSTER_ALL_FILE" + exit 1 +fi + +# Export kubeconfig +export KUBECONFIG=./kubeconfig + +# Verify cluster connectivity +echo "Verifying cluster connectivity..." +kubectl cluster-info + +# Create temp directory +mkdir -p "$TEMP_DIR" + +# Extract namespaces from cluster-all.yaml +echo "Extracting namespaces..." +NAMESPACES=$(grep "namespace:" "$CLUSTER_ALL_FILE" | sed 's/.*namespace: //' | sort -u) +echo "Found namespaces: $NAMESPACES" + +# Create namespaces +echo "Creating namespaces..." +for ns in $NAMESPACES; do + kubectl create namespace "$ns" --dry-run=client -o yaml | kubectl apply -f - 2>/dev/null || true +done + +# Extract and apply resources +echo "Extracting resources from cluster-all.yaml..." + +# Apply ServiceAccounts first +echo "Applying ServiceAccounts..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "ServiceAccount") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply RBAC +echo "Applying RBAC resources..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "Role" or .kind == "RoleBinding" or .kind == "ClusterRole" or .kind == "ClusterRoleBinding") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply ConfigMaps +echo "Applying ConfigMaps..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "ConfigMap") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply Secrets +echo "Applying Secrets..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "Secret") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply PersistentVolumeClaims +echo "Applying PersistentVolumeClaims..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "PersistentVolumeClaim") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply Services +echo "Applying Services..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "Service") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply Deployments +echo "Applying Deployments..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "Deployment") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply StatefulSets +echo "Applying StatefulSets..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "StatefulSet") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply Ingress +echo "Applying Ingress resources..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "Ingress") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Apply remaining resources (DaemonSet, CronJob, Job, etc.) +echo "Applying other resources..." +kubectl get -f "$CLUSTER_ALL_FILE" --dry-run=client -o json 2>/dev/null | \ + jq -r '.items[] | select(.kind == "DaemonSet" or .kind == "CronJob" or .kind == "Job" or .kind == "HorizontalPodAutoscaler") | del(.status, .metadata.resourceVersion, .metadata.uid, .metadata.creationTimestamp, .metadata.generation)' | \ + jq -s '.' | kubectl apply -f - 2>/dev/null || true + +# Cleanup +rm -rf "$TEMP_DIR" + +echo "=== Deployment Complete ===" + +# Show results +echo "" +echo "Namespaces:" +kubectl get ns + +echo "" +echo "Deployments:" +kubectl get deployments -A 2>/dev/null | head -40 + +echo "" +echo "StatefulSets:" +kubectl get statefulsets -A 2>/dev/null | head -20 + +echo "" +echo "Services:" +kubectl get svc -A 2>/dev/null | head -40 diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..a1821c9 --- /dev/null +++ b/main.tf @@ -0,0 +1,295 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + alicloud = { + source = "aliyun/alicloud" + version = "~> 1.224" + } + local = { + source = "hashicorp/local" + version = "~> 2.4" + } + } + + backend "local" { + path = "terraform.tfstate" + } +} + +provider "alicloud" { + region = var.region + access_key = var.access_key + secret_key = var.secret_key +} + +data "alicloud_zones" "sgp" { + available_resource_creation = "VSwitch" +} + +resource "alicloud_vpc" "this" { + vpc_name = "${var.project}-vpc" + cidr_block = var.vpc_cidr + + tags = var.tags +} + +resource "alicloud_vswitch" "this" { + count = 3 + + name = "${var.project}-vswitch-${count.index}" + vpc_id = alicloud_vpc.this.id + cidr_block = cidrsubnet(var.vpc_cidr, 8, count.index) + zone_id = data.alicloud_zones.sgp.zones[count.index].id + + tags = var.tags +} + +resource "alicloud_security_group" "this" { + security_group_name = "${var.project}-sg" + vpc_id = alicloud_vpc.this.id + + tags = var.tags +} + +resource "alicloud_security_group_rule" "allow_ssh" { + type = "ingress" + ip_protocol = "tcp" + port_range = "22/22" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_http" { + type = "ingress" + ip_protocol = "tcp" + port_range = "80/80" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_https" { + type = "ingress" + ip_protocol = "tcp" + port_range = "443/443" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_k8s_api" { + type = "ingress" + ip_protocol = "tcp" + port_range = "6443/6443" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_arangodb" { + type = "ingress" + ip_protocol = "tcp" + port_range = "8529/8529" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_mongodb" { + type = "ingress" + ip_protocol = "tcp" + port_range = "27017/27017" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_mysql" { + type = "ingress" + ip_protocol = "tcp" + port_range = "3306/3306" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_postgres" { + type = "ingress" + ip_protocol = "tcp" + port_range = "5432/5432" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_redis" { + type = "ingress" + ip_protocol = "tcp" + port_range = "6379/6379" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_elasticsearch" { + type = "ingress" + ip_protocol = "tcp" + port_range = "9200/9200" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_rabbitmq" { + type = "ingress" + ip_protocol = "tcp" + port_range = "5672/15672" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_kafka" { + type = "ingress" + ip_protocol = "tcp" + port_range = "9092/9092" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_kafka_zookeeper" { + type = "ingress" + ip_protocol = "tcp" + port_range = "2181/2181" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_kibana" { + type = "ingress" + ip_protocol = "tcp" + port_range = "5601/5601" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_prometheus" { + type = "ingress" + ip_protocol = "tcp" + port_range = "9090/9090" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_grafana" { + type = "ingress" + ip_protocol = "tcp" + port_range = "3000/3000" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_kubernetes_dashboard" { + type = "ingress" + ip_protocol = "tcp" + port_range = "8443/8443" + cidr_ip = "10.0.0.0/16" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_security_group_rule" "allow_nodeports" { + type = "ingress" + ip_protocol = "tcp" + port_range = "30000/32767" + cidr_ip = "0.0.0.0/0" + security_group_id = alicloud_security_group.this.id +} + +resource "alicloud_cs_managed_kubernetes" "this" { + name_prefix = "${var.project}-k8s" + cluster_spec = var.k8s_cluster_spec + vswitch_ids = alicloud_vswitch.this[*].id + security_group_id = alicloud_security_group.this.id + + service_cidr = "172.20.0.0/16" + pod_cidr = "172.21.0.0/16" + + new_nat_gateway = true + + timeouts { + delete = "60m" + } + + tags = var.tags +} + +resource "alicloud_cs_kubernetes_node_pool" "workers" { + cluster_id = alicloud_cs_managed_kubernetes.this.id + name = "${var.project}-workers" + node_pool_name = "${var.project}-workers" + instance_types = [var.k8s_worker_instance_type] + vswitch_ids = alicloud_vswitch.this[*].id + desired_size = var.k8s_worker_count + system_disk_category = var.k8s_worker_disk_category + system_disk_size = var.k8s_worker_disk_size + + scaling_config { + min_size = var.k8s_worker_count + max_size = var.k8s_max_worker_count + } + + data_disks { + category = var.k8s_data_disk_category + size = var.k8s_data_disk_size + } + + tags = var.tags +} + +resource "alicloud_slb" "this" { + name = "${var.project}-slb" + address_type = "internet" + vswitch_id = alicloud_vswitch.this[0].id + load_balancer_spec = "slb.s2.small" + + tags = var.tags +} + +resource "alicloud_slb_listener" "http" { + load_balancer_id = alicloud_slb.this.id + frontend_port = 80 + backend_port = 80 + protocol = "http" +} + +resource "alicloud_slb_listener" "https" { + count = var.ssl_certificate_id != "" ? 1 : 0 + load_balancer_id = alicloud_slb.this.id + frontend_port = 443 + backend_port = 443 + protocol = "https" +} + +resource "local_file" "kubeconfig" { + content = alicloud_cs_managed_kubernetes.this.kube_config + filename = "${path.module}/kubeconfig" +} + +output "cluster_id" { + value = alicloud_cs_managed_kubernetes.this.id +} + +output "cluster_name" { + value = alicloud_cs_managed_kubernetes.this.name +} + +output "slb_public_ip" { + value = alicloud_slb.this.address +} + +output "vpc_id" { + value = alicloud_vpc.this.id +} + +output "vswitch_ids" { + value = alicloud_vswitch.this[*].id +} + +output "worker_node_count" { + value = var.k8s_worker_count +} + +output "api_server_endpoint" { + value = "https://${alicloud_cs_managed_kubernetes.this.id}.${var.region}.alicloud.com:6443" +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..dbcee0b --- /dev/null +++ b/variables.tf @@ -0,0 +1,93 @@ +variable "region" { + description = "Alibaba Cloud region" + type = string + default = "ap-southeast-1" +} + +variable "access_key" { + description = "Alibaba Cloud access key" + type = string + sensitive = true +} + +variable "secret_key" { + description = "Alibaba Cloud secret key" + type = string + sensitive = true +} + +variable "project" { + description = "Project name prefix for resources" + type = string + default = "lingble-prod" +} + +variable "vpc_cidr" { + description = "CIDR block for VPC" + type = string + default = "10.0.0.0/16" +} + +variable "tags" { + description = "Tags to apply to all resources" + type = map(string) + default = { + Environment = "production" + ManagedBy = "terraform" + Cluster = "singapore-prod" + } +} + +variable "k8s_cluster_spec" { + description = "ACK cluster spec: ack.pro.small, ack.standard" + type = string + default = "ack.pro.small" +} + +variable "k8s_worker_instance_type" { + description = "Worker node instance type" + type = string + default = "ecs.s6-c2m2" +} + +variable "k8s_worker_count" { + description = "Number of worker nodes" + type = number + default = 10 +} + +variable "k8s_max_worker_count" { + description = "Maximum number of worker nodes (for autoscaling)" + type = number + default = 20 +} + +variable "k8s_worker_disk_category" { + description = "Worker node system disk category" + type = string + default = "cloud_ssd" +} + +variable "k8s_worker_disk_size" { + description = "Worker node system disk size in GB" + type = number + default = 100 +} + +variable "k8s_data_disk_category" { + description = "Worker node data disk category" + type = string + default = "cloud_ssd" +} + +variable "k8s_data_disk_size" { + description = "Worker node data disk size in GB" + type = number + default = 200 +} + +variable "ssl_certificate_id" { + description = "SSL certificate ID for SLB (from alicloud_ssl_certificate)" + type = string + default = "" +}