在我擔任Vance的 DevOps 工程師期間,我們在AWS Glue上執行了大約 80 個 ETL 管道,但隨著工作量的增加,我們的成本也隨之增加 — 達到了驚人的每月 10,000 美元。這是不可持續的。在分析了我們的管道後,我們意識到 AWS Glue 的無伺服器特性讓我們在空閒時間和不必要的運算上付出了巨大的代價
為了解決這個問題,我將我們的 ETL 工作負載遷移到Apache Airflow ,在使用 ECS 的 EC2 執行個體上執行,並使用 Terraform 協調一切。結果如何?成本降低了 96% ,使我們的帳單降至每月僅 400 美元,同時不影響性能。
雖然 Airflow 是 Glue 的絕佳替代品,但關於如何使用 Terraform 和 Celery Executor 進行正確設定它的文件很少——尤其是對於成本優化而言。本部落格將引導您了解我們是如何做到的、我們面臨的挑戰以及您如何採取相同的措施來削減 AWS Glue 成本。
不用說,這確實是一場戰爭,感謝我的經紀人Rishabh Lakhotia與我一起經歷了這場鬧劇,您確實是一位神。
我是 Akash Singh,來自班加羅爾的三年級工程專業學生和開源貢獻者。
我在網路上使用的名字是 SkySingh04。
從AWS Glue遷移到Apache Airflow涉及設定三個核心元件:
Web 伺服器– 用於管理 DAG(有向無環圖)和監控作業執行的 UI。
調度程序-負責觸發和調度 DAG 執行。
工作者-執行 DAG 中的實際任務。
使用Terraform ,我們配置了 ECS 來並行執行這三個並使它們能夠相互通信,接下來我們將介紹這一點。
一旦 Airflow 啟動並執行,下一步就是遷移我們的 ETL 工作流程。我們將把 Glue 作業放入Airflow DAG中,然後核對 Glue 作業,這是削減 AWS 成本的最後一步。
您可以使用以下 Dockerfile 並將其推送到 ECR 並在即將到來的配置中引用它:
FROM apache/airflow:latest-python3.9
USER root
RUN apt-get update && \
apt-get install -y --no-install-recommends \
git \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /opt/airflow/dags /opt/airflow/logs && \
chown -R airflow:root /opt/airflow && \
chmod -R 755 /opt/airflow/logs
USER airflow
RUN pip install --no-cache-dir \
apache-airflow-providers-github \
apache-airflow-providers-amazon \
apache-airflow-providers-mysql \
apache-airflow-providers-mongo \
apache-airflow[celery,redis] \
pandas
COPY --chown=airflow:root dags/* /opt/airflow/dags/
ENV AIRFLOW__LOGGING__BASE_LOG_FOLDER=/opt/airflow/logs \
AIRFLOW__LOGGING__WORKER_LOG_SERVER_PORT=8793 \
AIRFLOW__LOGGING__LOGGING_LEVEL=INFO \
AIRFLOW__LOGGING__LOG_FORMAT='[%(asctime)s] {%(filename)s:%(lineno)d} %(levelname)s - %(message)s' \
AIRFLOW__LOGGING__SIMPLE_LOG_FORMAT='%(asctime)s %(levelname)s - %(message)s' \
AIRFLOW__LOGGING__DAG_PROCESSOR_LOG_TARGET=file \
AIRFLOW__LOGGING__TASK_LOG_READER=task \
AIRFLOW__LOGGING__DAG_FILE_PROCESSOR_LOG_TARGET=/opt/airflow/logs/dag_processor_manager/dag_processor_manager.log \
AIRFLOW__LOGGING__DAG_PROCESSOR_MANAGER_LOG_LOCATION=/opt/airflow/logs/dag_processor_manager/dag_processor_manager.log
ENV AIRFLOW__CORE__DAGS_FOLDER=/opt/airflow/dags
RUN mkdir -p /opt/airflow/logs/scheduler \
/opt/airflow/logs/web \
/opt/airflow/logs/worker \
/opt/airflow/logs/dag_processor_manager \
/opt/airflow/logs/task_logs
USER root
RUN chown -R airflow:root /opt/airflow && \
chmod -R 755 /opt/airflow
USER airflow
這個 Dockerfile 將用於我們所有的三個元件,並且可以很好地設定日誌記錄。 DAG 直接嵌入到 Docker 映像中,我們稍後會講到這一點。建立映像、標記它、將其推送到 ECR 並進入下一步!
https://x.com/SkySingh04/status/1880591497571246356
可以編寫 Terraform 腳本來使用具有 EC2 啟動類型的 ECS(彈性容器服務)在 AWS 上設定Apache Airflow 。我們需要確保加入:
/ecs/airflow
)。允許應用程式負載平衡器 (ALB) 的入站 HTTP(連接埠 80)和 HTTPS(連接埠 443)流量。
啟用不受限制的出站流量。
使用 DNS 驗證為airflow.internal.example.com提供 ACM(AWS 憑證管理員)憑證。
設定 Route 53 DNS 記錄以將 Airflow URL 解析到 ALB。
為 Airflow 網路伺服器建立內部 ALB ,支援 IPv4 和 IPv6( dualstack
)。
設定 HTTP 偵聽器(連接埠 80)以將流量重新導向至 HTTPS(連接埠 443)。
設定 HTTPS 偵聽器(連接埠 443)以將請求轉送至 ECS 目標群組。
為在EC2 支援的 ECS 叢集上執行的Airflow Web 伺服器定義一個 ECS 任務。
使用儲存在 AWS ECR ( aws_ecr_repository.airflow.repository_url:latest
) 中的Docker 映像。
分配2GB記憶體( 2048MB
)。
將容器的8080連接埠對應到主機上以供Web存取。
定義健康檢查( http://localhost:8080/health
)。
建立一個名為「airflow-webserver」的 ECS 服務,並執行 1 項所需任務。
將 ECS 服務與ALB 目標群組關聯以實現負載平衡。
啟用execute-command
以允許透過 AWS SSM 進行偵錯。
使用容量提供者策略進行 ECS 資源管理。
airflow.internal.example.com
)。Terraform 腳本在 ECS 任務定義中包含幾個環境變數:
AIRFLOW__DATABASE__SQL_ALCHEMY_CONN
):指定 Airflow 元資料資料庫的PostgreSQL資料庫連接字串。
使用 AWS KMS 加密金鑰安全地儲存資料庫密碼。
_AIRFLOW_WWW_USER_CREATE
:確保建立預設的 Airflow Web 使用者。
_AIRFLOW_WWW_USER_USERNAME
:設定使用者名稱(預設值: airflow
)。
_AIRFLOW_WWW_USER_PASSWORD
:透過 AWS KMS 機密安全地儲存密碼。
AIRFLOW__WEBSERVER__EXPOSE_CONFIG
:允許透過 Web UI 公開 Airflow 設定。
AIRFLOW__SCHEDULER__ENABLE_HEALTH_CHECK
:啟用內建排程程式健康檢查。
_AIRFLOW_DB_MIGRATE
:確保 Airflow 在啟動時執行必要的資料庫遷移。現在繼續執行terraform plan
和terraform apply
,您應該會看到大量資源被建立。如果一切順利,您將在指定的 URL 上看到 airflow ui:
Airflow Scheduler負責協調 DAG 執行並確保計劃任務在正確的時間執行。可以編寫 Terraform 腳本將排程器配置為 ECS 服務,配置CloudWatch 日誌記錄,並啟用自動擴充以有效管理資源使用。
雖然其中大部分與網頁伺服器類似,但總而言之,我們需要加入:
在 CloudWatch ( /ecs/airflow-scheduler/
)中記錄調度程序執行。
透過 StatsD Metrics( airflow-metrics
命名空間)監控效能。
在具有自動擴展功能的 ECS 叢集中執行,確保高效的資源分配。
使用CloudWatch代理程式進行監控,協助分析任務執行時間。
由受限安全群組保護,阻止不必要的流量。
現在,繼續執行terraform plan
和terraform apply
, Airflow Scheduler將成功配置! 🚀
Airflow 工作服務會作為ECS 服務部署在 EC2 執行個體上,並根據記憶體使用率自動擴充。它執行Celery 工作者程序,執行來自 DAG 的任務,並且需要Redis ,我們將在接下來進行設定。
需要注意的重要事項是:
使用CeleryExecutor ,這意味著任務在工作人員之間分配。
日誌被傳送到CloudWatch進行監控。
根據記憶體利用率,工作者數量在0 到 5之間動態縮放。
每個工作程序都以ECS內部的容器執行,並由自動縮放策略進行管理,目標記憶體利用率為 60% 。
DUMB_INIT_SETSID=0
設定為處理 Celery 關閉的正確訊號傳播。
整個設定讓我哭泣,因為在 ECS 中除錯自動擴展、日誌管理和任務執行是一場噩夢。此外,Redis 還沒有出現,所以痛苦還遠遠沒有結束。
設定 redis 並不那麼糟糕,您可以使用以下 terraform 檔案:
resource "aws_elasticache_subnet_group" "airflow" {
name = "airflow-redis-subnet-group"
subnet_ids = aws_subnet.airflow[*].id
tags = merge(
{
name = "airflow-redis-subnet-group"
},
local.common_tags
)
}
resource "aws_security_group" "airflow_redis" {
name_prefix = "airflow-redis"
vpc_id = data.aws_vpc.this.id
tags = merge(
{
Name = "airflow-redis"
},
local.common_tags
)
}
resource "aws_security_group_rule" "airflow_redis_inbound" {
type = "ingress"
from_port = 6379
to_port = 6379
protocol = "tcp"
cidr_blocks = [data.aws_vpc.this.cidr_block]
security_group_id = aws_security_group.airflow_redis.id
description = "Allow Redis from internal network"
}
resource "aws_elasticache_cluster" "airflow" {
cluster_id = "airflow"
engine = "redis"
node_type = "cache.t4g.small"
num_cache_nodes = 1
parameter_group_name = "default.redis5.0"
engine_version = "5.0.6"
port = 6379
subnet_group_name = aws_elasticache_subnet_group.airflow.name
security_group_ids = [aws_security_group.airflow_redis.id]
tags = merge(
{
name = "airflow-redis-server"
},
local.common_tags
)
}
resource "aws_security_group_rule" "airflow_redis_outbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.airflow_redis.id
}
同樣,我們也將為氣流設定 RDS:
# Security Groups
resource "aws_security_group" "airflow_rds" {
lifecycle {
create_before_destroy = true
}
name_prefix = "airflow-rds-default-"
description = "Allow TLS inbound traffic and all outbound traffic for airflow"
vpc_id = data.aws_vpc.this.id
tags = {
Name = "airflow-rds-default"
}
}
resource "aws_security_group_rule" "airflow_rds_inbound" {
type = "ingress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = [data.aws_vpc.this.cidr_block]
security_group_id = aws_security_group.airflow_rds.id
description = "Allow all from internal network"
}
resource "aws_db_subnet_group" "airflow" {
name = "postgres-airflow"
subnet_ids = aws_subnet.airflow[*].id
}
resource "aws_db_instance" "airflow" {
db_name = "any db name"
apply_immediately = true
allocated_storage = "100"
storage_type = "gp3"
engine = "postgres"
engine_version = "17.2"
auto_minor_version_upgrade = true
instance_class = "db.t4g.micro"
username = "airflow"
password = data.aws_kms_secrets.airflow.plaintext["db_password"]
multi_az = false
publicly_accessible = false
deletion_protection = false
skip_final_snapshot = true
identifier = "airflow"
vpc_security_group_ids = [aws_security_group.airflow_rds.id]
db_subnet_group_name = aws_db_subnet_group.airflow.name
}
繼續使用 terraform 建立所有這些資源!
為了使Airflow 在 ECS 中與 CeleryExecutor 正常運作,需要幾個環境變數來進行日誌記錄、任務執行、資料庫連線、Redis 作為訊息代理程式以及外部整合。這些在Terraform 本地中定義並傳遞到 Airflow 容器中。
1️⃣核心氣流配置
實例名稱:
"AIRFLOW__WEBSERVER__INSTANCE_NAME" = "airflow-webserver"
協助辨識 Web 伺服器實例。
執行人:
"AIRFLOW__CORE__EXECUTOR" = "CeleryExecutor"
使用CeleryExecutor將任務指派給多個工作程序,而不是在單一實例中按順序執行它們。
資料庫連線:
"AIRFLOW__CORE__SQL_ALCHEMY_CONN"
使用儲存在 AWS KMS 機密中的憑證連接到PostgreSQL 。
負載範例:
"AIRFLOW__CORE__LOAD_EXAMPLES" = "True"
控制是否應載入範例 DAG。
2️⃣日誌配置(AWS CloudWatch 和 S3)
日誌等級:
"AIRFLOW__LOGGING__LOGGING_LEVEL" = "DEBUG"
啟用詳細日誌記錄以進行除錯。
遠端記錄至 CloudWatch:
"AIRFLOW__LOGGING__REMOTE_LOGGING" = "True"
"AIRFLOW__LOGGING__REMOTE_LOG_CONN_ID" = "aws_conn"
"AIRFLOW__LOGGING__REMOTE_BASE_LOG_FOLDER" = "s3://abc"
將日誌儲存在S3 和 CloudWatch中,即使容器重新啟動也能存取它們。
3️⃣ Celery & Redis 配置(訊息佇列 & 任務結果儲存)
訊息隊列(Redis):
"AIRFLOW__CELERY__BROKER_URL" = "redis://${aws_elasticache_cluster.airflow.cache_nodes[0].address}:6379/0"
Celery 使用Redis進行任務排隊(尚未設置,另一個痛苦來源)。
任務結果儲存(PostgreSQL):
"AIRFLOW__CELERY__RESULT_BACKEND" = "db+postgresql://airflow:${data.aws_kms_secrets.airflow.plaintext["db_password"]}@${aws_db_instance.airflow.endpoint}/airflow"
任務執行結果儲存於PostgreSQL中,確保持久化。
芹菜運送選項:
"AIRFLOW__CELERY_BROKER_TRANSPORT_OPTIONS__VISIBILITY_TIMEOUT" = "1800"
確保任務不會過早被標記為失敗。
4️⃣ SMTP(DAG 故障和通知的電子郵件警報)
SMTP 配置:
"AIRFLOW__SMTP__SMTP_HOST" = "d"
"AIRFLOW__SMTP__SMTP_MAIL_FROM" = "[email protected]"
"AIRFLOW__SMTP__SMTP_PORT" = "587"
"AIRFLOW__SMTP__SMTP_SSL" = "True"
用於透過電子郵件發送失敗通知。
5️⃣ AWS 特定配置
區域設定:
"AWS_DEFAULT_REGION" = local.region
確保 Terraform 和 Airflow 元件在正確的 AWS 區域中運作。
流暢的位元記錄以實現可觀察性:
使用Fluent Bit ( aws-for-fluent-bit:stable
) 進行日誌收集。
6️⃣ 外部整合(GitHub 和 AWS Secrets Manager)
GitHub 連線(Airflow 提供者):
"AIRFLOW__PROVIDERS__GITHUB__GITHUB_CONN_ID" = "github_default"
"AIRFLOW__PROVIDERS__GITHUB__ACCESS_TOKEN" = data.aws_kms_secrets.airflow.plaintext["github_token"]
使 Airflow DAG 能夠與 GitHub API 互動。
痛點😭
由於網路和 IAM 角色問題,為 Celery 設定 Redis非常麻煩。
在處理權限的同時除錯S3 和 CloudWatch 中的日誌儲存令人沮喪。
管理AWS Secrets Manager 和 KMS 憑證解密增加了複雜性。
根據Redis 佇列深度和 CPU/記憶體使用量自動擴充工作器需要微調。
現在 Airflow 基礎設施基本上已經設定好(除了Redis之外😭),是時候移動我們的 DAG 了。我們不是動態安裝它們,而是將它們直接烘焙到 Docker 映像中。這可確保執行 Airflow 調度程式或工作器的每個容器都預先載入DAG,而無需依賴外部儲存。
1️⃣ 我們如何將 DAG 嵌入到 Docker 映像中
在我們之前編寫的Dockerfile中,我們透過將 DAG 複製到容器內的/dags
目錄中來新增 DAG
https://x.com/SkySingh04/status/1886127124714766686
2️⃣為什麼要採用這種方法?
✅無需外部 DAG 儲存(如 S3、EFS 或 Git 同步)。
✅確保版本控制— DAG 是Docker 建置流程的一部分,因此每個部署都會獲得已知的DAG 版本。
✅簡化部署— 執行時無需額外步驟複製 DAG。
3️⃣ 建置並推送 Docker 映像
加入 DAG 後,我們將建置並推送映像:
docker build -t airflow-custom:latest .
docker tag airflow-custom:latest <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/airflow:latest
docker push <AWS_ACCOUNT_ID>.dkr.ecr.<AWS_REGION>.amazonaws.com/airflow:latest
4️⃣ 更新 ECS 以使用新映像
由於我們將 DAG 融入到影像中,我們只需更新 ECS 以提取最新影像,DAG 就會在那裡。
aws ecs update-service \
--cluster airflow-cluster \
--service airflow-scheduler \
--force-new-deployment
這會觸發調度程序的滾動重啟,確保新的 DAG 被載入。
DAG 除錯:如果 DAG 有語法錯誤,ECS 將循環重新啟動調度程序,直到問題修復。
熱重新加載?烘焙 DAG 意味著每次 DAG 更新時都要重新部署— — 目前這樣就沒問題,但我們以後可能會加入已掛載的捲或Git 同步。
烘焙之前測試 DAG:為了避免糟糕的部署,我們應該在將 DAG 加入到圖像之前在本地進行測試。
DAG 現在位於容器內,這意味著無需執行時複製,不會出現缺少 DAG 的問題,而且不用擔心任何事情— — 直到下一次火災發生。 🔥
https://x.com/SkySingh04/status/1886761377832034352
這花了兩天時間,但我們慢慢地完成了每一項 Glue 工作,一個接一個,就像狙擊手擊落目標一樣。每次關機都會面臨擔憂——Airflow 能否順利接管,還是我們又將陷入另一場除錯噩夢?
每次轉換時,我們都會看到 DAG 旋轉起來,監控任務執行情況,並祈禱 Celery 不會背叛我們。仔細檢查日誌、調整重試,還喝了無數杯咖啡。
最後,在戰爭結束時,數字說明了一切:
🚀 膠水作業費用減少 96% 。
🔥 Airflow 全面投入執行,任務在 ECS 工作者之間高效運作。
💀 Redis 幾乎讓我們失去理智,但它(勉強)活了下來。
這次遷移不只是一次部署;這是一場意志的較量,儘管困難重重,我們最終還是取得了勝利。如今,塵埃落定,我鬆了一口氣,休息了一下——這並不是因為戰爭結束了,而是因為下一場戰鬥即將來臨。
原文出處:https://dev.to/skysingh04/how-i-reduced-10000-monthly-aws-glue-bill-to-400-using-airflow-147k