Skip to content

Instantly share code, notes, and snippets.

@denoww
Last active August 20, 2025 17:27
Show Gist options
  • Save denoww/5044b8da36f85afabb920263925684e9 to your computer and use it in GitHub Desktop.
Save denoww/5044b8da36f85afabb920263925684e9 to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash
# ec2_to_loadbalancers.sh
set -euo pipefail
##################################
# portaria
##################################
# curl -fsSL https://gist.githubusercontent.com/denoww/5044b8da36f85afabb920263925684e9/raw | bash -s -- --tags "projetos=portaria" --instance-id i-0cf13c52cbcf250f8 --alb-name portaria-production-mtls --nlb-name portaria-network --ports "tcp:443:tcp:4443,tcp:80:tcp:3001,tcp:3001:tcp:3001,tcp:9000:tcp:9000,tcp:9001:tcp:9001,tcp:9005:tcp:9005,http:80:http:3001,https:443:https:4443" --tcp-healthy-threshold 3 --tcp-unhealthy-threshold 3 --region us-east-1 --vpc-id vpc-0232f866 --certificado-https-arn arn:aws:acm:us-east-1:516862767124:certificate/99570c7a-1fb8-4e70-b340-637f7bcaa535
##################################
##################################
# socket-server-seucondominio
##################################
# curl -fsSL https://gist.githubusercontent.com/denoww/5044b8da36f85afabb920263925684e9/raw | bash -s -- --tags "projetos=socket-server-seucondominio" --instance-id i-03a300eaa053f038a --alb-name socket-server-seucondominio --ports "http:80:http:5200,https:443:http:5200" --tcp-healthy-threshold 3 --tcp-unhealthy-threshold 3 --region us-east-1 --vpc-id vpc-0232f866 --certificado-https-arn arn:aws:acm:us-east-1:516862767124:certificate/99570c7a-1fb8-4e70-b340-637f7bcaa535
##################################
#############################################################
# EXEMPLO NETWORK LOADBALANCE:
#############################################################
# curl -fsSL https://gist.githubusercontent.com/denoww/5044b8da36f85afabb920263925684e9/raw | bash -s -- --tags "projetos=portaria" --alb-name teste-alb --nlb-name teste-nlb --ports "tcp:443:tcp:443,tcp:80:tcp:3001,tcp:3001:tcp:3001,tcp:9000:tcp:9000,tcp:9001:tcp:9001,tcp:9005:tcp:9005,https:443:https:4443,http:80:http:3001" --tcp-healthy-threshold 3 --tcp-unhealthy-threshold 3 --region us-east-1 --certificado-https-arn arn:aws:acm:us-east-1:516862767124:certificate/99570c7a-1fb8-4e70-b340-637f7bcaa535
# ou
# bash ec2_to_loadbalancers.sh --tags "projetos=portaria" --alb-name teste-alb --nlb-name teste-nlb --ports "tcp:443:tcp:4443,tcp:80:tcp:3001,tcp:3001:tcp:3001,tcp:9000:tcp:9000,tcp:9001:tcp:9001,tcp:9005:tcp:9005,https:443:https:4443,http:80:http:3001" --tcp-healthy-threshold 3 --tcp-unhealthy-threshold 3 --region us-east-1 --certificado-https-arn arn:aws:acm:us-east-1:516862767124:certificate/99570c7a-1fb8-4e70-b340-637f7bcaa535
#############################################################
# EXEMPLO APLICATION LOADBALANCE:
#############################################################
# bash ec2_to_loadbalancers.sh \
# --region us-east-1 \
# --alb-name meu_aplication_loadbalance \
# --ports "http:80:http:3000,https:443:https:443" \
# --certificado-https-arn arn:aws:acm:us-east-1:123456789012:certificate/abcd-efgh-...
#############################################################
# EXEMPLO NETWORK LB:
#############################################################
# bash ec2_to_loadbalancers.sh \
# --tags "projetos=xxxxx"
# --region us-east-1 \
# --nlb-name meu_network_loadbalance \
# --ports "tcp:9000:tcp:9000,tcp:9001:tcp:9001" \
# --certificado-https-arn arn:aws:acm:us-east-1:123456789012:certificate/abcd-efgh-...
# Uso:
# bash ec2_to_loadbalancers.sh \
# --region us-east-1 \
# --vpc-id vpc-0123456789abcdef0 \ # opcional; descobre via IMDS se não passar
# --instance-id i-0123456789abcdef0 \ # opcional; descobre via IMDS se não passar
# --alb-name meu_aplication_loadbalance \ # ou --alb-arn arn:aws:elasticloadbalancing:...
# --nlb-name meu_network_loadbalance \ # ou --nlb-arn ...
# --ports "http:80,https:443:http:3001,tcp:80:tcp:3001,tcp:3001:tcp,tcp:9000:tcp:9000"
#
# OBS sobre --ports:
# - proto:port -> listener_port = target_port = port
# - proto:listener_port:target_port -> listener_port e target_port distintos
# Exemplos: tcp:80:3001 | https:443:http:3001 | http:8080:3000
#
# HTTPS:
# --certificado-https-arn arn:aws:acm:... # obrigatório se usar 'https:' em --ports
#
# Health checks HTTP/HTTPS (ALB):
# --health-path "/health" --matcher "200-399" --health-interval 15 --health-timeout 5 \
# --healthy-threshold 2 --unhealthy-threshold 2
#
# Health checks TCP (NLB):
# (usa TCP padrão; opcionalmente --tcp-healthy-threshold/--tcp-unhealthy-threshold)
STEP=0
log() { printf '[%(%H:%M:%S)T] %s\n' -1 "$*" >&2; }
step() { STEP=$((STEP+1)); log "[$STEP] $*"; }
# AWS CLI: sem pager
export AWS_PAGER=""
AWS_OPTS_BASE=(--no-cli-pager)
REGION="us-east-1"
VPC_ID=""
INSTANCE_ID=""
ALB_NAME=""
ALB_ARN=""
NLB_NAME=""
NLB_ARN=""
PORTS_SPEC=""
CERT_ARN=""
HEALTH_PATH="/health"
MATCHER="200-399"
HEALTH_INTERVAL=15
HEALTH_TIMEOUT=5
HEALTHY_THRESHOLD=2
UNHEALTHY_THRESHOLD=2
TCP_HEALTHY_THRESHOLD=3
TCP_UNHEALTHY_THRESHOLD=3
TAGS_SPEC=""
# --------- Args ----------
while [[ $# -gt 0 ]]; do
case "$1" in
--region) REGION="$2"; shift 2;;
--vpc-id) VPC_ID="$2"; shift 2;;
--instance-id) INSTANCE_ID="$2"; shift 2;;
--alb-name) ALB_NAME="$2"; shift 2;;
--alb-arn) ALB_ARN="$2"; shift 2;;
--nlb-name) NLB_NAME="$2"; shift 2;;
--nlb-arn) NLB_ARN="$2"; shift 2;;
--ports) PORTS_SPEC="$2"; shift 2;;
--certificado-https-arn) CERT_ARN="$2"; shift 2;;
--health-path) HEALTH_PATH="$2"; shift 2;;
--matcher) MATCHER="$2"; shift 2;;
--health-interval) HEALTH_INTERVAL="$2"; shift 2;;
--health-timeout) HEALTH_TIMEOUT="$2"; shift 2;;
--healthy-threshold) HEALTHY_THRESHOLD="$2"; shift 2;;
--unhealthy-threshold) UNHEALTHY_THRESHOLD="$2"; shift 2;;
--tcp-healthy-threshold) TCP_HEALTHY_THRESHOLD="$2"; shift 2;;
--tcp-unhealthy-threshold) TCP_UNHEALTHY_THRESHOLD="$2"; shift 2;;
--tags) TAGS_SPEC="$2"; shift 2;;
*) echo "Arg inválido: $1"; exit 1;;
esac
done
[[ -z "$PORTS_SPEC" ]] && { echo "ERRO: passe --ports"; exit 1; }
AWS_OPTS=("${AWS_OPTS_BASE[@]}" --region "$REGION")
need() { command -v "$1" >/dev/null 2>&1 || { echo "Falta $1"; exit 1; }; }
need aws; need jq; need curl
# --------- IMDSv2 (se necessário) ----------
# --------- IMDSv2 (com timeout + feedback) ----------
imds_token() { curl -fsS --connect-timeout 1 --max-time 2 -X PUT \
"http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 60"; }
imds_get() { curl -fsS --connect-timeout 1 --max-time 2 \
-H "X-aws-ec2-metadata-token: $IMDS_TOKEN" \
"http://169.254.169.254/latest/meta-data/$1"; }
step "Resolvendo INSTANCE_ID e VPC_ID (IMDS ou parâmetros)"
IMDS_TOKEN="$(imds_token || true)"
if [[ -z "$INSTANCE_ID" ]]; then
if [[ -n "$IMDS_TOKEN" ]]; then
INSTANCE_ID="$(imds_get instance-id || true)"
fi
fi
if [[ -z "$VPC_ID" ]]; then
if [[ -n "$IMDS_TOKEN" ]]; then
MAC="$(imds_get network/interfaces/macs/ | head -n1 | tr -d '/' || true)"
[[ -n "$MAC" ]] && VPC_ID="$(imds_get "network/interfaces/macs/${MAC}/vpc-id" || true)"
fi
fi
[[ -z "$INSTANCE_ID" ]] && { echo "ERRO: não consegui obter INSTANCE_ID (passe --instance-id)"; exit 1; }
[[ -z "$VPC_ID" ]] && { echo "ERRO: não consegui obter VPC_ID (passe --vpc-id)"; exit 1; }
log "INSTANCE_ID=$INSTANCE_ID VPC_ID=$VPC_ID"
# --------- Resolve ALB/NLB ARN ----------
step "Resolvendo ARNs/Nomes dos Load Balancers"
if [[ -z "$ALB_ARN" && -n "$ALB_NAME" ]]; then
ALB_ARN="$(aws elbv2 describe-load-balancers "${AWS_OPTS[@]}" --names "$ALB_NAME" \
| jq -r '.LoadBalancers[0].LoadBalancerArn')"
fi
if [[ -z "$NLB_ARN" && -n "$NLB_NAME" ]]; then
NLB_ARN="$(aws elbv2 describe-load-balancers "${AWS_OPTS[@]}" --names "$NLB_NAME" \
| jq -r '.LoadBalancers[0].LoadBalancerArn')"
fi
# Descobre o "LoadBalancerName" a partir do ARN, se necessário
if [[ -n "$ALB_ARN" && -z "$ALB_NAME" ]]; then
ALB_NAME="$(aws elbv2 describe-load-balancers "${AWS_OPTS[@]}" --load-balancer-arns "$ALB_ARN" \
| jq -r '.LoadBalancers[0].LoadBalancerName')"
fi
if [[ -n "$NLB_ARN" && -z "$NLB_NAME" ]]; then
NLB_NAME="$(aws elbv2 describe-load-balancers "${AWS_OPTS[@]}" --load-balancer-arns "$NLB_ARN" \
| jq -r '.LoadBalancers[0].LoadBalancerName')"
fi
build_aws_tags_args() {
local spec="$1"
[[ -z "$spec" ]] && return 0
local -a out=()
IFS=',' read -r -a kvs <<< "$spec"
for kv in "${kvs[@]}"; do
kv="$(echo "$kv" | xargs)"
[[ -z "$kv" ]] && continue
k="${kv%%=*}"
v="${kv#*=}"
[[ -z "$k" ]] && continue
out+=("Key=$k,Value=$v")
done
printf '%s ' "${out[@]}"
}
AWS_TAGS_ARGS="$(build_aws_tags_args "$TAGS_SPEC")"
# Helper para montar nome do TG com limite de 32 chars
build_tg_name() {
local prefix="$1" proto="$2" port="$3"
local proto_lc="$(echo "$proto" | tr '[:upper:]' '[:lower:]')"
local name="tg-${prefix}-${proto_lc}-${port}"
local max=32
if ((${#name} > max)); then
local over=$((${#name} - max))
local prelen=${#prefix}
if (( prelen > over )); then
prefix="${prefix:0:$((prelen - over))}"
else
prefix="${prefix:0:1}"
fi
name="tg-${prefix}-${proto_lc}-${port}"
fi
printf '%s' "$name"
}
# Prefixos: para HTTP/HTTPS use o nome do ALB; para TCP use o nome do NLB
TG_PREFIX_HTTP="${ALB_NAME:-alb}"
TG_PREFIX_TCP="${NLB_NAME:-nlb}"
# --------- Helpers ----------
ensure_tg_alb_http() {
local target_port="$1" proto="$2" # proto = HTTP|HTTPS (target)
local tg_name; tg_name="$(build_tg_name "${TG_PREFIX_HTTP}" "$proto" "$target_port")"
local tg_arn
log "TG ${tg_name} → garantindo (ALB/$proto port=$target_port)"
tg_arn="$(aws elbv2 describe-target-groups "${AWS_OPTS[@]}" --names "$tg_name" 2>/dev/null \
| jq -r '.TargetGroups[0].TargetGroupArn' || true)"
if [[ -z "$tg_arn" || "$tg_arn" == "null" ]]; then
tg_arn="$(aws elbv2 create-target-group "${AWS_OPTS[@]}" \
--name "$tg_name" \
--protocol "$proto" \
--port "$target_port" \
--vpc-id "$VPC_ID" \
--target-type instance \
--health-check-protocol "$proto" \
--health-check-path "$HEALTH_PATH" \
--health-check-interval-seconds "$HEALTH_INTERVAL" \
--health-check-timeout-seconds "$HEALTH_TIMEOUT" \
--healthy-threshold-count "$HEALTHY_THRESHOLD" \
--unhealthy-threshold-count "$UNHEALTHY_THRESHOLD" \
--matcher "HttpCode=$MATCHER" \
| jq -r '.TargetGroups[0].TargetGroupArn')"
echo "Criado TG (ALB/$proto) $tg_name" >&2
[[ -n "$AWS_TAGS_ARGS" ]] && aws elbv2 add-tags "${AWS_OPTS[@]}" --resource-arns "$tg_arn" --tags $AWS_TAGS_ARGS >/dev/null
else
aws elbv2 modify-target-group "${AWS_OPTS[@]}" \
--target-group-arn "$tg_arn" \
--health-check-protocol "$proto" \
--health-check-path "$HEALTH_PATH" \
--health-check-interval-seconds "$HEALTH_INTERVAL" \
--health-check-timeout-seconds "$HEALTH_TIMEOUT" \
--healthy-threshold-count "$HEALTHY_THRESHOLD" \
--unhealthy-threshold-count "$UNHEALTHY_THRESHOLD" \
--matcher "HttpCode=$MATCHER" >/dev/null
echo "TG (ALB/$proto) $tg_name já existia; atualizado." >&2
fi
aws elbv2 register-targets "${AWS_OPTS[@]}" \
--target-group-arn "$tg_arn" \
--targets "Id=$INSTANCE_ID,Port=$target_port" >/dev/null
printf '%s\n' "$tg_arn"
}
ensure_listener_alb_http() {
local listener_port="$1" proto_listener="$2" tg_arn="$3" # proto_listener = HTTP|HTTPS
[[ -z "$ALB_ARN" ]] && { echo "ERRO: ALB não informado"; exit 1; }
local listener_arn
listener_arn="$(aws elbv2 describe-listeners "${AWS_OPTS[@]}" --load-balancer-arn "$ALB_ARN" \
| jq -r --arg p "$listener_port" '.Listeners[] | select(.Port == ($p|tonumber)) | .ListenerArn')"
if [[ -z "$listener_arn" || "$listener_arn" == "null" ]]; then
if [[ "$proto_listener" == "HTTPS" ]]; then
[[ -z "$CERT_ARN" ]] && { echo "ERRO: --certificado-https-arn obrigatório para HTTPS:$listener_port"; exit 1; }
listener_arn="$(aws elbv2 create-listener "${AWS_OPTS[@]}" \
--load-balancer-arn "$ALB_ARN" \
--protocol HTTPS \
--port "$listener_port" \
--certificates "CertificateArn=$CERT_ARN" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" \
| jq -r '.Listeners[0].ListenerArn')"
else
listener_arn="$(aws elbv2 create-listener "${AWS_OPTS[@]}" \
--load-balancer-arn "$ALB_ARN" \
--protocol HTTP \
--port "$listener_port" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" \
| jq -r '.Listeners[0].ListenerArn')"
fi
echo "Criado listener ALB $proto_listener:$listener_port"
[[ -n "$AWS_TAGS_ARGS" ]] && aws elbv2 add-tags "${AWS_OPTS[@]}" --resource-arns "$listener_arn" --tags $AWS_TAGS_ARGS >/dev/null
else
aws elbv2 modify-listener "${AWS_OPTS[@]}" \
--listener-arn "$listener_arn" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" >/dev/null
echo "Listener ALB $proto_listener:$listener_port já existia; default action atualizada."
fi
}
ensure_tg_nlb() {
local proto_target="$1" target_port="$2" # proto_target = TCP|TLS
local proto_lc="$(echo "$proto_target" | tr '[:upper:]' '[:lower:]')"
local tg_name; tg_name="$(build_tg_name "${TG_PREFIX_TCP}" "$proto_lc" "$target_port")"
local tg_arn
tg_arn="$(aws elbv2 describe-target-groups "${AWS_OPTS[@]}" --names "$tg_name" 2>/dev/null \
| jq -r '.TargetGroups[0].TargetGroupArn' || true)"
if [[ -z "$tg_arn" || "$tg_arn" == "null" ]]; then
tg_arn="$(aws elbv2 create-target-group "${AWS_OPTS[@]}" \
--name "$tg_name" \
--protocol "$proto_target" \
--port "$target_port" \
--vpc-id "$VPC_ID" \
--target-type instance \
--healthy-threshold-count "$TCP_HEALTHY_THRESHOLD" \
--unhealthy-threshold-count "$TCP_UNHEALTHY_THRESHOLD" \
| jq -r '.TargetGroups[0].TargetGroupArn')"
echo "Criado TG (NLB/$proto_target) $tg_name" >&2
[[ -n "$AWS_TAGS_ARGS" ]] && aws elbv2 add-tags "${AWS_OPTS[@]}" --resource-arns "$tg_arn" --tags $AWS_TAGS_ARGS >/dev/null
else
aws elbv2 modify-target-group "${AWS_OPTS[@]}" \
--target-group-arn "$tg_arn" \
--healthy-threshold-count "$TCP_HEALTHY_THRESHOLD" \
--unhealthy-threshold-count "$TCP_UNHEALTHY_THRESHOLD" >/dev/null
echo "TG (NLB/$proto_target) $tg_name já existia; atualizado." >&2
fi
log "TG ${tg_name} → registrando alvo Id=$INSTANCE_ID,Port=$target_port"
aws elbv2 register-targets "${AWS_OPTS[@]}" \
--target-group-arn "$tg_arn" \
--targets "Id=$INSTANCE_ID,Port=$target_port" >/dev/null
printf '%s\n' "$tg_arn"
}
ensure_listener_nlb() {
local proto_listener="$1" listener_port="$2" tg_arn="$3" # proto_listener = TCP|TLS
[[ -z "$NLB_ARN" ]] && { echo "ERRO: NLB não informado"; exit 1; }
local listener_arn
listener_arn="$(aws elbv2 describe-listeners "${AWS_OPTS[@]}" --load-balancer-arn "$NLB_ARN" \
| jq -r --arg p "$listener_port" '.Listeners[] | select(.Port == ($p|tonumber)) | .ListenerArn')"
if [[ -z "$listener_arn" || "$listener_arn" == "null" ]]; then
if [[ "$proto_listener" == "TLS" ]]; then
[[ -z "$CERT_ARN" ]] && { echo "ERRO: --certificado-https-arn obrigatório para TLS:$listener_port no NLB"; exit 1; }
listener_arn="$(aws elbv2 create-listener "${AWS_OPTS[@]}" \
--load-balancer-arn "$NLB_ARN" \
--protocol TLS \
--port "$listener_port" \
--certificates "CertificateArn=$CERT_ARN" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" \
| jq -r '.Listeners[0].ListenerArn')"
else
listener_arn="$(aws elbv2 create-listener "${AWS_OPTS[@]}" \
--load-balancer-arn "$NLB_ARN" \
--protocol TCP \
--port "$listener_port" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" \
| jq -r '.Listeners[0].ListenerArn')"
fi
echo "Criado listener NLB $proto_listener:$listener_port"
[[ -n "$AWS_TAGS_ARGS" ]] && aws elbv2 add-tags "${AWS_OPTS[@]}" --resource-arns "$listener_arn" --tags $AWS_TAGS_ARGS >/dev/null
else
aws elbv2 modify-listener "${AWS_OPTS[@]}" \
--listener-arn "$listener_arn" \
--default-actions "Type=forward,TargetGroupArn=$tg_arn" >/dev/null
echo "Listener NLB $proto_listener:$listener_port já existia; default action atualizada."
fi
}
# --------- Processa PORTS_SPEC ----------
# --------- Processa PORTS_SPEC ----------
IFS=',' read -r -a ITEMS <<< "$PORTS_SPEC"
for raw in "${ITEMS[@]}"; do
item="$(echo "$raw" | xargs)" # trim
[[ -z "$item" ]] && continue
IFS=':' read -r proto_listener listener_port proto_target target_port <<< "$item"
# valida formato: sempre 4 campos
if [[ -z "$proto_listener" || -z "$listener_port" || -z "$proto_target" || -z "$target_port" ]]; then
echo "Entrada inválida '$item' (esperado proto_listener:listener_port:proto_target:target_port)"; exit 1
fi
# normaliza
proto_listener="$(echo "$proto_listener" | tr '[:upper:]' '[:lower:]')"
proto_target="$(echo "$proto_target" | tr '[:upper:]' '[:lower:]')"
# valida portas
[[ ! "$listener_port" =~ ^[0-9]+$ || ! "$target_port" =~ ^[0-9]+$ ]] && { echo "Portas inválidas em '$item'"; exit 1; }
# mapeia para enums da AWS
case "$proto_listener" in
http) L_PROTO="HTTP" ;;
https) L_PROTO="HTTPS" ;;
tcp) L_PROTO="TCP" ;;
tls) L_PROTO="TLS" ;;
*) echo "Protocolo listener inválido em '$item'"; exit 1;;
esac
case "$proto_target" in
http) T_PROTO="HTTP" ;;
https) T_PROTO="HTTPS" ;;
tcp) T_PROTO="TCP" ;;
tls) T_PROTO="TLS" ;;
*) echo "Protocolo target inválido em '$item'"; exit 1;;
esac
if [[ "$L_PROTO" == "HTTP" || "$L_PROTO" == "HTTPS" ]]; then
# ALB: targets devem ser HTTP/HTTPS
if [[ "$T_PROTO" != "HTTP" && "$T_PROTO" != "HTTPS" ]]; then
echo "ALB não suporta target $T_PROTO em '$item' (use HTTP/HTTPS)"; exit 1
fi
tg_arn="$(ensure_tg_alb_http "$target_port" "$T_PROTO")"
ensure_listener_alb_http "$listener_port" "$L_PROTO" "$tg_arn"
else
# NLB: listeners TCP/TLS; targets TCP/TLS
if [[ "$T_PROTO" != "TCP" && "$T_PROTO" != "TLS" ]]; then
echo "NLB exige target TCP/TLS em '$item'"; exit 1
fi
tg_arn="$(ensure_tg_nlb "$T_PROTO" "$target_port")"
ensure_listener_nlb "$L_PROTO" "$listener_port" "$tg_arn"
fi
done
echo "OK: ALB/NLB garantidos para: $PORTS_SPEC"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment