Terraform部署后无法通过Application Load Balancer访问后端实例
我用Terraform在AWS的3个不同可用区部署了3个运行简单Web应用的实例,测试阶段通过实例公网IP能正常访问应用。但添加Application Load Balancer(ALB)后,访问ALB域名时收到错误:Hmmm… can't reach this page alb-1860663581.eu-central-1.elb.amazonaws.com refused to connect.
我尝试过修改ALB监听端口并在安全组中放行、移除实例公网IP改为私有IP,但实例到ALB DNS的Telnet均失败,不过目标组健康检查显示实例状态正常。甚至在遵循AWS基础教程部署ELB时也遇到相同问题,以下是我的配置代码:
Main.tf
#PROVIDERS provider "aws" { access_key = var.aws_access_key secret_key = var.aws_secret_key region = var.aws_region } #DATA data "aws_ssm_parameter" "ami" { name = "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2" } #RESOURCES #KEY PAIR ##NETWORKING resource "aws_vpc" "vpc" { cidr_block = "172.32.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = local.common_tags } resource "aws_internet_gateway" "igw" { vpc_id = aws_vpc.vpc.id tags = local.common_tags } resource "aws_subnet" "public_subnet" { count = var.subnet_count.public cidr_block = var.public_subnet_cidr_blocks[count.index] vpc_id = aws_vpc.vpc.id map_public_ip_on_launch = true availability_zone = var.public_subnet_availability_zones[count.index] tags = local.common_tags } resource "aws_subnet" "private_subnets" { count = var.subnet_count.private cidr_block = var.private_subnet_cidr_blocks[count.index] vpc_id = aws_vpc.vpc.id availability_zone = var.private_subnet_availability_zones[count.index] tags = local.common_tags } ##ROUTING resource "aws_route_table" "rtb-pub" { vpc_id = aws_vpc.vpc.id route { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.igw.id } tags = local.common_tags } resource "aws_route_table_association" "rta-public" { count = var.subnet_count.public subnet_id = aws_subnet.public_subnet[count.index].id route_table_id = aws_route_table.rtb-pub.id } resource "aws_route_table" "rtb-priv" { vpc_id = aws_vpc.vpc.id tags = local.common_tags } resource "aws_route_table_association" "rta-priv" { count = var.subnet_count.private subnet_id = aws_subnet.private_subnets[count.index].id route_table_id = aws_route_table.rtb-priv.id } #SECURITY GROUPS #PHP-SG resource "aws_security_group" "php-sg" { name = "php-sg" vpc_id = aws_vpc.vpc.id # HTTP access from anywhere which also includes the LB's listener port ingress { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } ingress { description = "Allow SSH from my PC" from_port = "22" to_port = "22" protocol = "tcp" cidr_blocks = ["${var.my_ip}/32"] } ingress { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] } # ingress { # description = "Load balancer listener port" # from_port = 90 # to_port = 90 # protocol = "tcp" # cidr_blocks = ["0.0.0.0/0"] # } # outbound internet access egress { from_port = 0 to_port = 0 protocol = "-1" cidr_blocks = ["0.0.0.0/0"] } tags = local.common_tags } resource "aws_security_group" "rds-sg" { name = "rds-sg" vpc_id = aws_vpc.vpc.id ingress { description = "Allow MySQL traffic from only the php sg" from_port = "3306" to_port = "3306" protocol = "tcp" security_groups = [aws_security_group.php-sg.id] } tags = local.common_tags } # APPLICATION LOAD BALANCER RESOURCES# resource "aws_lb_target_group" "tg" { name = "targetGroup" port = 80 target_type = "instance" vpc_id = aws_vpc.vpc.id protocol = "HTTP" } resource "aws_lb_target_group_attachment" "tgatt" { count = var.subnet_count.public target_group_arn = aws_lb_target_group.tg.arn port = 80 target_id = aws_instance.phpserver[count.index].id #availability_zone = var.public_subnet_availability_zones } resource "aws_lb" "lb" { name = "ALB" internal = false load_balancer_type = "application" security_groups = [ aws_security_group.php-sg.id ] subnets = aws_subnet.public_subnet.*.id } resource "aws_lb_listener" "listener" { load_balancer_arn = aws_lb.lb.arn port = "80" protocol = "HTTP" default_action { type = "redirect" redirect { port = "443" protocol = "HTTPS" status_code = "HTTP_301" } } } resource "aws_lb_listener_rule" "listener_rule" { listener_arn = aws_lb_listener.listener.arn priority = 100 action { type = "forward" target_group_arn = aws_lb_target_group.tg.arn } condition { path_pattern { values = [ "/var/www/html/index.php" ] } } } # INSTANCE # resource "aws_instance" "phpserver" { count = var.subnet_count.public ami = nonsensitive(data.aws_ssm_parameter.ami.value) instance_type = "t3.micro" subnet_id = aws_subnet.public_subnet[count.index].id associate_public_ip_address = true vpc_security_group_ids = [aws_security_group.php-sg.id] key_name = "someKeyName" user_data = templatefile("${path.module}/startup_script.tftpl", { endpoint = aws_db_instance.default.endpoint db_name = aws_db_instance.default.db_name }) tags = local.common_tags depends_on = [ aws_db_instance.default ] } # RDS Subnet group # resource "aws_db_subnet_group" "db_subnet_group" { name = "db_subnet_group" subnet_ids = [for subnet in aws_subnet.private_subnets : subnet.id] } # RDS # resource "aws_db_instance" "default" { db_subnet_group_name = "db_subnet_group" allocated_storage = 20 db_name = "livanoDB" engine = "mysql" instance_class = "db.t3.micro" username = var.db_username password = var.db_password skip_final_snapshot = true vpc_security_group_ids = [aws_security_group.rds-sg.id] tags = local.common_tags depends_on = [ aws_db_subnet_group.db_subnet_group ] }
Variables.tf
variable "aws_access_key" { type = string description = "AWS Access key" sensitive = true } variable "aws_secret_key" { type = string description = "AWS Secret key" sensitive = true } variable "db_username" { description = "Database administrator username" type = string sensitive = true } variable "db_password" { description = "Database administrator password" type = string sensitive = true } variable "aws_region" { type = string description = "AWS Region to use for resources" default = "eu-central-1" } variable "my_ip" { description = "my IP Address" type = string sensitive = true } variable "owner" { type = string description = "Owner of the deployed resource in the VPC" default = "livano" } variable "public_subnet_cidr_blocks" { description = "CIDR blocks for public subnets" type = list(string) default = ["172.32.0.0/19", "172.32.32.0/19", "172.32.64.0/19", "172.32.96.0/19"] } variable "private_subnet_cidr_blocks" { description = "CIDR blocks for private subnets" type = list(string) default = ["172.32.224.0/19", "172.32.192.0/19", "172.32.160.0/19", "172.32.128.0/19"] } variable "private_subnet_availability_zones" { description = "list of eu-central-1 availability zones" type = list(string) default = ["eu-central-1a", "eu-central-1b", "eu-central-1c", ] } variable "public_subnet_availability_zones" { description = "list of eu-central-1 availability zones" type = list(string) default = ["eu-central-1a", "eu-central-1b", "eu-central-1c", ] } variable "subnet_count" { description = "Number of subnets" type = map(number) default = { public = 3, private = 2 } }
你的配置存在3个核心问题导致ALB无法正常访问:
1. ALB监听配置逻辑错误
当前HTTP监听(80端口)的默认动作是重定向到HTTPS 443端口,但你没有配置443端口的监听规则。外部用户访问ALB的80端口时,会被强制重定向到443,但ALB未开启该端口监听,直接导致连接被拒绝。
同时,你添加的路径规则使用了实例本地文件路径/var/www/html/index.php,这是错误的——ALB的路径规则匹配的是URL路径,而非服务器本地文件路径。
修复监听配置(测试HTTP访问场景)
修改aws_lb_listener和aws_lb_listener_rule,取消重定向,直接转发请求到目标组,并修正路径规则:
resource "aws_lb_listener" "listener" { load_balancer_arn = aws_lb.lb.arn port = "80" protocol = "HTTP" # 默认动作直接转发到目标组,而非重定向 default_action { type = "forward" target_group_arn = aws_lb_target_group.tg.arn } } # 修正路径规则为URL路径,或直接删除该规则使用默认转发 resource "aws_lb_listener_rule" "listener_rule" { listener_arn = aws_lb_listener.listener.arn priority = 100 action { type = "forward" target_group_arn = aws_lb_target_group.tg.arn } condition { path_pattern { # 改为URL路径,比如匹配/index.php values = ["/index.php"] } } }
2. 目标组健康检查配置缺失(可选但推荐)
虽然你提到目标组健康检查正常,但显式配置健康检查路径能避免后续因应用路径变更导致的异常。在aws_lb_target_group中添加健康检查规则:
resource "aws_lb_target_group" "tg" { name = "targetGroup" port = 80 target_type = "instance" vpc_id = aws_vpc.vpc.id protocol = "HTTP" health_check { path = "/" # 匹配你的Web应用根路径 protocol = "HTTP" port = "traffic-port" interval = 30 timeout = 5 healthy_threshold = 2 unhealthy_threshold = 2 matcher = "200-399" } }
3. 实例到ALB的Telnet失败无需关注
ALB是面向客户端的反向代理,实例不需要主动访问ALB的DNS地址,Telnet失败属于正常现象,只要目标组健康检查正常,说明ALB能正常访问实例的80端口。
- 应用上述修改后,执行
terraform apply更新配置; - 等待ALB状态变为
active,目标组所有实例状态为healthy; - 访问ALB的HTTP域名,即可正常转发到实例的Web应用。
内容的提问来源于stack exchange,提问作者livanov

