Ingress annotations provisions unnecessary AWS classic load balancer - amazon-web-services

Within my AWS EKS cluster provisioning an AWS application load balancer using annotations on the Ingress object. Additionally an unnecessary classic load balancer is being provisioned. Any ideas or best practice on how to prevent this?
resource "kubernetes_service" "api" {
metadata {
name = "${var.project_prefix}-api-service"
}
spec {
selector = {
app = "${var.project_prefix}-api"
}
port {
name = "http"
port = 80
target_port = 1337
}
port {
name = "https"
port = 443
target_port = 1337
}
type = "LoadBalancer"
}
}
resource "kubernetes_ingress" "api" {
wait_for_load_balancer = true
metadata {
name = "${var.project_prefix}-api"
annotations = {
"kubernetes.io/ingress.class" = "alb"
"alb.ingress.kubernetes.io/scheme" = "internet-facing"
"alb.ingress.kubernetes.io/target-type" = "instance"
"alb.ingress.kubernetes.io/certificate-arn" = local.api-certificate_arn
"alb.ingress.kubernetes.io/load-balancer-name" = "${var.project_prefix}-api"
"alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\": 80}, {\"HTTPS\":443}]"
"alb.ingress.kubernetes.io/actions.ssl-redirect" = "{\"Type\": \"redirect\", \"RedirectConfig\": { \"Protocol\": \"HTTPS\", \"Port\": \"443\", \"StatusCode\": \"HTTP_301\"}}"
}
}
spec {
backend {
service_name = kubernetes_service.api.metadata.0.name
service_port = 80
}
rule {
http {
path {
path = "/*"
backend {
service_name = "ssl-redirect"
service_port = "use-annotation"
}
}
}
}
}
}

Your LoadBalancer service is responsible for deploying the classic load balancer, and if you just need an application load balancer, is unnecessary.
resource "kubernetes_service" "api" {
metadata {
name = "${var.project_prefix}-api-service"
}
spec {
selector = {
app = "${var.project_prefix}-api"
}
port {
name = "http"
port = 80
target_port = 1337
}
port {
name = "https"
port = 443
target_port = 1337
}
type = "ClusterIP" # See comments below
}
}
resource "kubernetes_ingress" "api" {
wait_for_load_balancer = true
metadata {
name = "${var.project_prefix}-api"
annotations = {
"kubernetes.io/ingress.class" = "alb"
"alb.ingress.kubernetes.io/target-type" = "ip" # See comments below
"alb.ingress.kubernetes.io/scheme" = "internet-facing"
"alb.ingress.kubernetes.io/target-type" = "instance"
"alb.ingress.kubernetes.io/certificate-arn" = local.api-certificate_arn
"alb.ingress.kubernetes.io/load-balancer-name" = "${var.project_prefix}-api"
"alb.ingress.kubernetes.io/listen-ports" = "[{\"HTTP\": 80}, {\"HTTPS\":443}]"
"alb.ingress.kubernetes.io/actions.ssl-redirect" = "{\"Type\": \"redirect\", \"RedirectConfig\": { \"Protocol\": \"HTTPS\", \"Port\": \"443\", \"StatusCode\": \"HTTP_301\"}}"
}
}
spec {
backend {
service_name = kubernetes_service.api.metadata.0.name
service_port = 80
}
rule {
http {
path {
path = "/*"
backend {
service_name = "ssl-redirect"
service_port = "use-annotation"
}
}
}
}
}
}
Traffic Modes
Depending on your cluster and networking setup, you might be able to use ip target type, where the load balancer can communicate directly with Kubernetes pods via their IP (so ClusterIP service types are fine) if you have a CNI configuration, or use instance in conjunction with NodePort service types as the load balancer cannot directly access the pod IPs. Some relevant links below:
ALB Target Types
VPC CNI EKS Plugin
Load Balancer Types
Some relevant links regarding Kubernetes load balancing and EKS load balancers. Note that Ingress resources are layer 7 and load balance service resources are layer 4, hence ALBs deployed for EKS ingress resources and NLBs for load balanced service resources:
Rancher Kubernetes Load Balancers
AWS Load Balancer Comparison

Related

504 Gateway Timeout while API calls in Next.js app deployed through Terraform to ECS EC2 task

My issue is about making a calls to API that is written in Next.js app. The Web app is on the same task of Next.js app. My aplication is deployed to AWS ECS with Application Load Balancer and EC2 launch type task. My task has configured network mode with using awsvpc. I'm able to connect through HTTPS and that's prove that Security Groups are configured well.
The egress rules are set up for any protocol and any port. While using pages were are made a calls to Next.js API, I'm receiving 504 Gateway Timeout.
Infrastructure has been uploaded using Terraform and I have created VPC and networking stuff also using terraform. The requests are also communicating with Cognito and DynamoDB but IAM policies are added to IAM Task Role
Do you have any ideas what could be an issue?
I have tried checking routing table rules with Internet Gateway and it still doesn't help.
EDIT:
Here is my terraform code. I'm using HTTPS connection with certificate generated in AWS ACM
#####
# VPC and subnets
#####
data "aws_vpc" "default_vpc" {
default = true
}
data "aws_subnets" "all_default_subnets" {
filter {
name = "vpc-id"
values = [data.aws_vpc.default_vpc.id]
}
}
#####
# ALB
#####
resource "aws_lb" "alb" {
name = "alb"
load_balancer_type = "application"
security_groups = [ aws_security_group.alb.id ]
subnets = data.aws_subnets.all_default_subnets.ids
enable_deletion_protection = false
}
resource "aws_lb_target_group" "lb_target_group" {
name = "target-alb-name"
port = "80"
protocol = "HTTP"
vpc_id = data.aws_vpc.default_vpc.id
target_type = "ip"
health_check {
healthy_threshold = "3"
interval = "10"
path = "/api/health"
protocol = "HTTP"
unhealthy_threshold = "3"
}
}
resource "aws_lb_listener" "alb_80" {
load_balancer_arn = aws_lb.alb.arn
port = "443"
protocol = "HTTPS"
# My certificate generated from terraform level
certificate_arn = aws_acm_certificate.loadbalancer_cert.arn
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.lb_target_group.arn
}
tags = {
environment = "prod"
}
}
##############################################################
# AWS ECS-SERVICE
##############################################################
resource "aws_ecs_service" "service" {
cluster = aws_ecs_cluster.cluster.id
desired_count = 1
launch_type = "EC2"
name = "service"
task_definition = aws_ecs_task_definition.task_definition.arn
force_new_deployment = true
deployment_minimum_healthy_percent = 0
network_configuration {
subnets = data.aws_subnets.all_default_subnets.ids
security_groups = [aws_security_group.ecs_tasks.id]
assign_public_ip = false
}
load_balancer {
container_name = "ecs-container"
container_port = 3000
target_group_arn = aws_lb_target_group.lb_target_group.arn
}
depends_on = [aws_lb_listener.alb_80]
}
############################################################
# AWS ECS-TASK
############################################################
resource "aws_ecs_task_definition" "task_definition" {
container_definitions = data.template_file.task_definition_json.rendered
execution_role_arn = var.execution_task_role_arn
family = "web-task"
network_mode = "awsvpc"
cpu = "128"
memory = "256"
requires_compatibilities = ["EC2"]
task_role_arn = var.prod_task_role_arn
}
data "template_file" "task_definition_json" {
template = file("${path.module}/task_definition.json")
vars = {
BASE_URL = var.BASE_URL,
APP_AWS_REGION = var.APP_AWS_REGION,
COGNITO_WEB_CLIENT_ID = var.COGNITO_WEB_CLIENT_ID,
COGNITO_USER_POOL_ID = var.COGNITO_USER_POOL_ID,
AWS_ACCESS_KEY_ID = var.AWS_ACCESS_KEY_ID,
AWS_SECRET_ACCESS_KEY = var.AWS_SECRET_ACCESS_KEY,
DYNAMODB_TABLE_NAME = var.DYNAMODB_TABLE_NAME,
COGNITO_REGION = var.COGNITO_REGION,
}
}
###########################################################
# AWS ECS-EC2
###########################################################
module "ec2_instance" {
source = "terraform-aws-modules/ec2-instance/aws"
version = "~> 4.3.0"
name = "ec2-instance"
create_spot_instance = false
iam_instance_profile = aws_iam_instance_profile.ecs_agent.name
user_data = data.template_file.user_data.rendered
ami = var.prod_ami_id
instance_type = var.prod_instance_type
key_name = "key-pairs"
monitoring = true
vpc_security_group_ids = [aws_security_group.ecs_tasks.id]
subnet_id = data.aws_subnets.all_default_subnets.ids[0]
root_block_device = [
{
volume_type = "gp3"
volume_size = 30
delete_on_termination = true
}
]
tags = {
environment = "prod"
}
}
data "template_file" "user_data" {
template = file("${path.module}/user_data.tpl")
}
resource "aws_security_group" "ecs_tasks" {
name = "sg-ecs-tasks"
vpc_id = data.aws_vpc.default_vpc.id
ingress {
protocol="tcp"
from_port=3000
to_port=3000
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=443
to_port=443
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=80
to_port=80
cidr_blocks=["0.0.0.0/0"]
}
egress {
protocol="-1"
from_port=0
to_port=0
cidr_blocks=["0.0.0.0/0"]
}
}
#####
# Security Group Config
#####
resource "aws_security_group" "ecs_tasks" {
name = "sg-ecs-tasks"
vpc_id = data.aws_vpc.default_vpc.id
ingress {
protocol="tcp"
from_port=3000
to_port=3000
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=443
to_port=443
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=80
to_port=80
cidr_blocks=["0.0.0.0/0"]
}
egress {
protocol="-1"
from_port=0
to_port=0
cidr_blocks=["0.0.0.0/0"]
}
}
resource "aws_security_group" "alb" {
name = "sg-alb"
vpc_id = data.aws_vpc.default_vpc.id
ingress {
protocol="tcp"
from_port=3000
to_port=3000
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=443
to_port=443
cidr_blocks=["0.0.0.0/0"]
}
ingress {
protocol="tcp"
from_port=80
to_port=80
cidr_blocks=["0.0.0.0/0"]
}
egress {
protocol="-1"
from_port=0
to_port=0
cidr_blocks=["0.0.0.0/0"]
}
}
user_data for EC2 instance
#!/bin/bash
cat <<'EOF' >> /etc/ecs/ecs.config
ECS_CLUSTER=ecs-cluster
ECS_ENABLE_SPOT_INSTANCE_DRAINING=true
ECS_ENABLE_CONTAINER_METADATA=true
ECS_AVAILABLE_LOGGING_DRIVERS=["json-file","awslogs"]
ECS_IMAGE_PULL_BEHAVIOR=always
EOF
yum update -y
yum install -y httpd
sudo systemctl start httpd
sudo systemctl enable httpd
Based on quesstion asked under the link I've noticed that I can use bridge network mode and it really works. However based on AWS Best Practices it's recommended to use awsvpc mode. Would anyone mind help me fond a solution how to figure it out?

Timeout on LoadBalancer exposing ECS services

I'm currently trying to expose 2 services from my ECS cluster on a loadbalancer with a HTTP listener redirecting to a HTTPS listener (which is forwarding to the target groups of my services, depending on the host-header).
I registered the certificates with AWS Certificate Manager, but unfortunately I get timeouts when I'm trying to hit the URLS on my browser.
I am deploying those resources with Terraform :
resource "aws_lb_target_group" "nginx-tg" {
name = "nginx-tg"
target_type = "ip"
protocol = "HTTP"
port = "80"
vpc_id = var.vpc-id
}
resource "aws_lb_target_group" "apache2-tg" {
name = "apache2-tg"
target_type = "ip"
protocol = "HTTP"
port = "80"
vpc_id = var.vpc-id
}
resource "aws_lb_listener_certificate" "nginx-certificate" {
certificate_arn = var.nginx-certificate-arn # subdomain1.domain.com
listener_arn = aws_lb_listener.test-cluster-lb-https-listener.arn
}
resource "aws_lb_listener_certificate" "apache2-certificate" {
certificate_arn = var.apache2-certificate-arn # subdomain2.domain.com
listener_arn = aws_lb_listener.test-cluster-lb-https-listener.arn
}
resource "aws_lb_listener" "test-cluster-lb-https-listener" {
load_balancer_arn = aws_lb.test-cluster-elb.arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = var.listener-certificate-arn
default_action {
type = "redirect"
redirect {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
host = "home.domain.com"
path = "/"
}
}
}
resource "aws_lb_listener_rule" "nginx-listener-rule" {
listener_arn = aws_lb_listener.test-cluster-lb-https-listener.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.nginx-tg.arn
}
condition {
host_header {
values = [var.nginx-url]
}
}
priority = 1
}
resource "aws_lb_listener_rule" "apache2-listener-rule" {
listener_arn = aws_lb_listener.test-cluster-lb-https-listener.arn
action {
type = "forward"
target_group_arn = aws_lb_target_group.apache2-tg.arn
}
condition {
host_header {
values = [var.apache2-url]
}
}
priority = 2
}
And here is the ECS services declaration :
resource "aws_ecs_service" "nginx" {
name = "nginx"
launch_type = "FARGATE"
cluster = aws_ecs_cluster.ecs_cluster.id
task_definition = aws_ecs_task_definition.nginx_task.arn
desired_count = 2
network_configuration {
subnets = [var.private-sn-az-1, var.private-sn-az-2]
security_groups = [aws_security_group.service-sg.id]
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.nginx-tg.arn
container_name = "nginx"
container_port = 80
}
}
resource "aws_ecs_service" "apache2" {
name = "apache2"
launch_type = "FARGATE"
cluster = aws_ecs_cluster.ecs_cluster.id
task_definition = aws_ecs_task_definition.apache2_task.arn
desired_count = 2
network_configuration {
subnets = [var.private-sn-az-1, var.private-sn-az-2]
security_groups = [aws_security_group.service-sg.id]
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.apache2-tg.arn
container_name = "apache2"
container_port = 80
}
}
The security group of the services allows all the requests from my loadbalancer on port 80 !
It's my first post so I may have been unclear on some points, if you need the rest of the resources declaration feel free to ask me :)
Thank you in advance !!
What I tried : Target group was exposing port 8080, switched it to 80.
What I expected : Thought that my service was only listening on port 80, so changing it would make the timeout go.
What actually happened : Still got a timeout when i'm trying to accesss the urls (subdomain1.domain.com & subdomain2.domain.com)...

How to create AWS ALB using kubernetes_ingress terraform resource?

I'm trying to deploy an Application Load Balancer to AWS using Terraform's kubernetes_ingress resource:
I'm using aws-load-balancer-controller which I've installed using helm_release resource to my cluster.
Now I'm trying to deploy a deployment with a service and ingress.
This is how my service looks like:
resource "kubernetes_service" "questo-server-service" {
metadata {
name = "questo-server-service-${var.env}"
namespace = kubernetes_namespace.app-namespace.metadata.0.name
}
spec {
selector = {
"app.kubernetes.io/name" = lookup(kubernetes_deployment.questo-server.metadata.0.labels, "app.kubernetes.io/name")
}
port {
port = 80
target_port = 4000
}
type = "LoadBalancer"
}
}
And this is how my ingress looks like:
resource "kubernetes_ingress" "questo-server-ingress" {
wait_for_load_balancer = true
metadata {
name = "questo-server-ingress-${var.env}"
namespace = kubernetes_namespace.app-namespace.metadata.0.name
annotations = {
"kubernetes.io/ingress.class" = "alb"
"alb.ingress.kubernetes.io/target-type" = "instance"
}
}
spec {
rule {
http {
path {
path = "/*"
backend {
service_name = kubernetes_service.questo-server-service.metadata.0.name
service_port = 80
}
}
}
}
}
}
The issue is that when I run terraform apply it creates a Classic Load Balancer instead of an Application Load Balancer.
I've tried changing service's type to NodePort but it didn't help.
I've also tried with adding more annotations to ingress like "alb.ingress.kubernetes.io/load-balancer-name" = "${name}" but then the it created two load balancers at once! One internal ALB and one internet facing CLB.
Any ideas how I can create an internet facing Application Load Balancer using this setup?
--- Update ----
I've noticed, that actually, the service is the Classic Load Balancer via which I can connect to my deployment.
Ingress creates an ALB, but it's prefixed with internal, so my questions here is, how to create an internet facing ALB?
Thanks!
Try using the alb.ingress.kubernetes.io/scheme: internet-facing annotation.
You find a list of all available annotations here: https://kubernetes-sigs.github.io/aws-load-balancer-controller/v2.2/guide/ingress/annotations/
Answering my own question, like most of the times :)
This is the proper setup, just in case someone come across it:
The service's type has to be NodePort:
resource "kubernetes_service" "questo-server-service" {
metadata {
name = "questo-server-service-${var.env}"
namespace = kubernetes_namespace.app-namespace.metadata.0.name
}
spec {
selector = {
"app.kubernetes.io/name" = lookup(kubernetes_deployment.questo-server.metadata.0.labels, "app.kubernetes.io/name")
}
port {
port = 80
target_port = 4000
}
type = "NodePort"
}
}
And ingress's annotation has to be set as follows: (you can ingnore load-balancer-name and healthcheck-pass as they are not relevant to the question:
resource "kubernetes_ingress" "questo-server-ingress" {
wait_for_load_balancer = true
metadata {
name = "questo-server-ingress-${var.env}"
namespace = kubernetes_namespace.app-namespace.metadata.0.name
annotations = {
"kubernetes.io/ingress.class" = "alb"
"alb.ingress.kubernetes.io/target-type" = "ip"
"alb.ingress.kubernetes.io/scheme" = "internet-facing"
"alb.ingress.kubernetes.io/load-balancer-name" = "questo-server-alb-${var.env}"
"alb.ingress.kubernetes.io/healthcheck-path" = "/health"
}
}
spec {
rule {
http {
path {
path = "/*"
backend {
service_name = kubernetes_service.questo-server-service.metadata.0.name
service_port = 80
}
}
}
}
}
}

How to configure multiple target groups and listeners aws with terraform?

my plan
alb-->fe-server-->alb-->be-server
Frontend ihave on angular and in main.js baseurl on ABL
Backend maven working on port 8080
two listeners 80 and 8080 ports
resource "aws_lb_listener" "listener_http_fe" {
load_balancer_arn = aws_lb.alb_fe_be.arn
port = 80
...
resource "aws_lb_listener_rule" "listener_rule_fe" {
listener_arn = aws_lb_listener.listener_http_fe.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.target_group_fe.arn
}
condition {
path_pattern {
values = ["*"]
}
...
resource "aws_lb_listener" "listener_http_be" {
load_balancer_arn = aws_lb.alb_fe_be.arn
port = 8080
protocol = "HTTP"
...
resource "aws_lb_listener_rule" "listener_rule_be" {
listener_arn = aws_lb_listener.listener_http_be.arn
priority = 101
...
two target groups
resource "aws_lb_target_group" "target_group_fe" {
name = 12
port = 80
protocol = "HTTP"
...
resource "aws_lb_target_group" "target_group_be" {
name = 11
port = 8080 #port to be-server(8080)
...
How i need to configure because working only fe-server or be-server separately but not together with ALB?

Is it possible to make a Codedeploy BlueGreen deployment to ECS use healthcheck data?

I haven't been able to do this. I have healthchecks configured on my NLB's two target groups and they report healthy, but the deployment will take place and succeed even if the target group that is being switched to never becomes healthy.
I can use alarms and lambda based tests to rollback of course, but I'm not sure if I can create an alarm that targets the currently non-serving-prod target group and not the currently serving-prod one in order to rollback the deployment properly.
It seems that it's not possible to use anything but CodeDeployDefault.ECSAllAtOnce for an ECS green/blue, and I also see that its not possible at all to define minimum healthy hosts in ECS deployment configurations, so does this mean that CodeDeploy doesn't consider health status at all when deploying ECS?
Here's my codedeploy terraform:
resource "aws_codedeploy_deployment_group" "deployment_group" {
app_name = aws_codedeploy_app.application.name
deployment_group_name = "${var.project_name}-deployment-group"
service_role_arn = aws_iam_role.codedeploy_role.arn
deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
# Automatically rollback on failure, alarm, or request.
# See: https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_AutoRollbackConfiguration.html
auto_rollback_configuration {
enabled = "true"
events = ["DEPLOYMENT_FAILURE", "DEPLOYMENT_STOP_ON_ALARM", "DEPLOYMENT_STOP_ON_REQUEST"]
}
blue_green_deployment_config {
# See: https://docs.aws.amazon.com/codedeploy/latest/APIReference/API_DeploymentReadyOption.html
deployment_ready_option {
action_on_timeout = "CONTINUE_DEPLOYMENT"
}
terminate_blue_instances_on_deployment_success {
action = "TERMINATE"
# How many minutes to wait before terminating old instances
termination_wait_time_in_minutes = 1
}
}
# These settings are required for ECS deployments.
deployment_style {
deployment_option = "WITH_TRAFFIC_CONTROL"
deployment_type = "BLUE_GREEN"
}
# What to deploy
ecs_service {
cluster_name = aws_ecs_cluster.cluster.name
service_name = aws_ecs_service.service.name
}
load_balancer_info {
target_group_pair_info {
# The path used by a load balancer to route production traffic when an Amazon ECS deployment is complete.
prod_traffic_route {
listener_arns = [aws_lb_listener.listener.arn]
}
# Blue group is the original group at first.
target_group {
name = aws_lb_target_group.blue_group.name
}
# Green group is the one that gets switched to at first.
target_group {
name = aws_lb_target_group.green_group.name
}
}
}
}
And here is my target groups:
resource "aws_lb_target_group" "blue_group" {
name = "${var.project_name}-${var.stage}-b"
port = 8080
protocol = "TCP"
target_type = "ip"
vpc_id = var.vpc_id
health_check {
path = var.nlb_healthcheck_path
port = var.container_port
protocol = "HTTP"
healthy_threshold = 2
unhealthy_threshold = 2
interval = 10
}
stickiness {
enabled = false
type = "lb_cookie"
}
}
resource "aws_lb_target_group" "green_group" {
name = "${var.project_name}-${var.stage}-g"
port = 8080
protocol = "TCP"
target_type = "ip"
vpc_id = var.vpc_id
health_check {
path = aws_lb_target_group.blue_group.health_check[0].path
port = aws_lb_target_group.blue_group.health_check[0].port
protocol = aws_lb_target_group.blue_group.health_check[0].protocol
healthy_threshold = aws_lb_target_group.blue_group.health_check[0].healthy_threshold
unhealthy_threshold = aws_lb_target_group.blue_group.health_check[0].unhealthy_threshold
interval = aws_lb_target_group.blue_group.health_check[0].interval
}
stickiness {
enabled = false
type = "lb_cookie"
}
}