(5/5) Airflow on EKS — ECR 로 이미지 굽고 GitHub Actions 로 자동화
📚 Airflow on K8s (Helm) 시리즈 (전체 5편)
- (1/5) Airflow on K8s 시리즈 개요 — Helm 으로 올리고 워커는 컨테이너로 띄운다
- (2/5) Helm 으로 Airflow 를 K8s 에 설치하기 — KubernetesExecutor 셋업
- (3/5) Airflow 워커 이미지 만들고 pod_template_file 로 묶기
- (4/5) Airflow on K8s 운영 — git-sync · 로그 영속화 · 모니터링 · 스케일
- (5/5) Airflow on EKS — ECR 로 이미지 굽고 GitHub Actions 로 자동화 ← 지금 글
Summary
(1/5) ~ (4/5) 가 일반 K8s 위에서의 Airflow 였다면, 이번 편은 같은 셋업을 AWS 위로 옮겨요. 세 조각을 하나로 잇는 게 목표예요.
- EKS — Airflow 가 돌아가는 K8s 클러스터
- ECR — 워커가 쓸 Airflow 이미지를 보관할 사설 레지스트리
- GitHub Actions — 코드 푸시 한 번에 이미지가 ECR 로 올라가게 하는 CI
(3/5) 의 Dockerfile 과 (4/5) 의 운영 체크리스트는 그대로 살아있어요. 여기선 레지스트리/권한/자동화 만 AWS 식으로 교체해요.
💡 이 글에서 다루는 것
- 큰 그림 — GitHub Actions → ECR → EKS Airflow 한 그림
- ECR 레포 만들기
- EKS 가 ECR 을 풀할 수 있게 하기 (노드 IAM 정책 한 줄)
- GitHub Actions 가 ECR 로 푸시하게 하기 (OIDC + IAM Role)
- Helm values 에 ECR 이미지 박고
helm upgrade- DAG 한 번 굴려서 워커 Pod 가 ECR 이미지로 뜨는지 확인
1. 전체 흐름 한 그림
[GitHub Repo]
│ git push (main)
▼
[GitHub Actions]
│ OIDC 로 IAM Role assume
│ aws ecr login → docker build → docker push
▼
[Amazon ECR] 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/airflow:<sha>
│
│ EKS 노드가 ECR 에서 이미지 풀
▼
[EKS Cluster — Namespace: airflow]
├─ scheduler / api-server / triggerer / dag-processor (Deployment)
└─ Worker Pod (KubernetesExecutor 가 태스크마다 새로 띄움)
← 같은 ECR 태그 이미지로 뜸
핵심은 이미지 태그가 한 줄로 흐른다 는 거예요. GitHub 의 커밋 SHA 가 그대로 ECR 태그가 되고, 그 태그를 Helm values.yaml 의 defaultAirflowTag 가 받아요. 코드와 워커 이미지의 버전이 항상 1:1 로 묶입니다.
2. 전제 — EKS 클러스터와 Namespace
이 글은 EKS 클러스터가 이미 있다고 가정해요. 새로 만들 거면 가장 흔한 패턴은 eksctl 한 줄이에요.
eksctl create cluster \
--name data-platform \
--region ap-northeast-2 \
--version 1.30 \
--nodegroup-name workers \
--node-type m6i.xlarge \
--nodes 2 --nodes-min 2 --nodes-max 6 \
--managed
(2/5) 와 똑같이 Airflow 용 Namespace 와 두 개의 Secret 부터 박아둬요.
aws eks update-kubeconfig --name data-platform --region ap-northeast-2
kubectl create namespace airflow
# Fernet / API server 시크릿은 (2/5) 의 명령 그대로
💡 EKS 도 그냥 K8s 라서 (2/5) ~ (4/5) 의 명령이 그대로 먹어요. AWS 색이 진해지는 건 다음 섹션부터예요.
3. ECR 레포 만들기
워커 이미지를 보관할 사설 레지스트리예요. 한 번만 만들면 돼요.
aws ecr create-repository \
--repository-name airflow \
--region ap-northeast-2 \
--image-scanning-configuration scanOnPush=true \
--image-tag-mutability IMMUTABLE
옵션 두 개 짚어둘게요.
scanOnPush=true— 푸시할 때마다 ECR 이 알아서 취약점 스캔.IMMUTABLE— 같은 태그를 두 번 못 쓰게 강제. (3/5) 에서 봤듯:latest같이 가변 태그를 쓰면 노드별 캐시가 어긋나서 같은 태그인데 다른 이미지가 떠있는 사고가 나요. IMMUTABLE 이면 처음부터 그런 사고가 불가능해요.
레지스트리 호스트는 <aws-account-id>.dkr.ecr.<region>.amazonaws.com 모양으로 고정이에요. 예시는 이렇게 가정할게요.
123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/airflow
4. EKS 가 ECR 을 풀할 수 있게
ECR 은 사설 레지스트리지만 EKS 노드는 노드 IAM 역할에 정책 한 줄만 있으면 자격증명 없이 풀해요. 사설 레지스트리인데 imagePullSecrets 가 필요 없는 게 ECR + EKS 조합의 큰 장점이에요.
확인 포인트는 노드 그룹의 IAM Role 에 AmazonEC2ContainerRegistryReadOnly 정책이 붙어있는지예요. eksctl 로 만든 클러스터는 기본으로 붙어있어요.
# 노드 그룹의 IAM Role 확인
aws eks describe-nodegroup \
--cluster-name data-platform \
--nodegroup-name workers \
--query 'nodegroup.nodeRole' --output text
# 출력 예: arn:aws:iam::123456789012:role/eksctl-data-platform-nodegroup-...
# 그 Role 에 정책이 붙어있나
aws iam list-attached-role-policies \
--role-name <위에서_나온_role_name> \
--query 'AttachedPolicies[].PolicyName'
# 출력에 AmazonEC2ContainerRegistryReadOnly 가 있으면 OK
없으면 한 줄 추가.
aws iam attach-role-policy \
--role-name <node-role-name> \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly
✅ 이게 끝이에요. EKS Pod 의
imagePullSecrets안 채워도 ECR 풀이 됩니다. (3/5) 에서 사설 Harbor 쓸 때docker-registry시크릿을 박았던 단계가 통째로 사라져요.
5. GitHub Actions 가 ECR 로 푸시하게
여기가 이번 글의 진짜 본편이에요. 두 단계로 나뉘어요.
- AWS 쪽 — GitHub OIDC 를 신뢰하는 IAM Role 만들기 (영구 액세스 키 박지 않기 위해서)
- GitHub Actions 쪽 — 그 Role 을 assume → ECR 로그인 → 빌드/푸시
5-1. GitHub OIDC Provider 등록
처음 한 번만.
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 6938fd4d98bab03faadb97b34396831e3780aea1
5-2. ECR 푸시 권한을 가진 IAM Role
trust-policy.json 으로 GitHub Actions 만 이 Role 을 assume 할 수 있게 잠가요.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:dorumugs/airflow-images:ref:refs/heads/main"
}
}
}]
}
🚨
sub의repo:<owner>/<repo>:ref:refs/heads/<branch>가 가장 중요한 보안 줄이에요. 이 줄 없이*로 풀어두면 누구의 GitHub Actions 든 이 Role 을 assume 할 수 있게 돼요. 반드시 본인 repo + 브랜치로 고정.
Role 을 만들고 ECR 푸시 정책을 붙여요.
aws iam create-role \
--role-name github-actions-airflow-ecr \
--assume-role-policy-document file://trust-policy.json
aws iam attach-role-policy \
--role-name github-actions-airflow-ecr \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
생성된 Role ARN 을 메모해둬요. 예: arn:aws:iam::123456789012:role/github-actions-airflow-ecr.
5-3. GitHub Actions 워크플로
레포 안 .github/workflows/build-airflow-image.yml.
name: Build & Push Airflow Image
on:
push:
branches: [main]
paths:
- 'Dockerfile'
- 'requirements.txt'
- '.github/workflows/build-airflow-image.yml'
permissions:
id-token: write # OIDC 토큰 발급에 필수
contents: read
env:
AWS_REGION: ap-northeast-2
ECR_REPOSITORY: airflow
ROLE_ARN: arn:aws:iam::123456789012:role/github-actions-airflow-ecr
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: $
aws-region: $
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build & push
env:
REGISTRY: $
TAG: 3.1.8-py311-$
run: |
docker build -t $REGISTRY/$ECR_REPOSITORY:$TAG .
docker push $REGISTRY/$ECR_REPOSITORY:$TAG
echo "image=$REGISTRY/$ECR_REPOSITORY:$TAG" >> $GITHUB_OUTPUT
핵심 한 줄은 id-token: write 권한이에요. 이게 없으면 OIDC 토큰이 안 발급돼서 configure-aws-credentials 가 실패해요.
태그는 베이스 Airflow 버전 + 커밋 SHA 패턴이 깔끔해요. 어떤 베이스 위에서 어떤 코드로 만들어진 이미지인지 한눈에 잡혀요.
✅ 푸시 후 ECR 콘솔에서 새 태그가 보이면 1차 성공.
aws ecr describe-images --repository-name airflow로도 확인 가능.
6. Helm values 에 ECR 이미지 박기
(3/5) 의 values.yaml 에서 레지스트리 주소만 ECR 로 바꾸면 끝이에요. imagePullSecrets 가 필요 없는 게 ECR + EKS 의 미덕.
# values.yaml (변경)
defaultAirflowRepository: 123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/airflow
defaultAirflowTag: "3.1.8-py311-<sha>" # GHA 가 푸시한 태그 그대로
airflowVersion: "3.1.8"
# (3/5) 의 registry.secretName 블록은 통째로 삭제 — ECR 은 노드 IAM 으로 풀
배포는 (2/5) 와 같아요.
helm upgrade --install airflow apache-airflow/airflow \
--namespace airflow \
--values values.yaml \
--timeout 10m
kubectl -n airflow rollout status deploy/airflow-scheduler
💡 자동 helm upgrade 까지 잇고 싶으면 GitHub Actions 잡 끝에 한 단계 더 붙이면 돼요. 흔한 패턴 두 가지.
- values 파일을 같은 repo 에 두고 GHA 가
sed -i "s/defaultAirflowTag:.*/defaultAirflowTag: \"$TAG\"/" values.yaml한 뒤 자동 커밋 → Argo CD 가 감지해서 배포.- GHA 가 직접
helm upgrade— kubeconfig 발급용 IAM 권한과aws eks update-kubeconfig단계를 추가. 단순한 환경엔 충분하지만 클러스터 자격증명을 CI 가 직접 들고 다닌다는 부담이 있어요.
7. 워커 Pod 가 ECR 이미지로 뜨는지 확인
가벼운 DAG 한 개 굴려놓고 (3/5) 의 확인 절차를 그대로 돌려요.
kubectl -n airflow get pods -w \
| grep -v -E "scheduler|api-server|triggerer|dag-processor|postgresql"
태스크가 도는 동안 워커 Pod 를 잡아서 describe 찍어봐요.
kubectl -n airflow describe pod <worker-pod-name>
확인 포인트.
Image:가123456789012.dkr.ecr.ap-northeast-2.amazonaws.com/airflow:3.1.8-py311-<sha>Successfully pulled image이벤트가 떴고ImagePullBackOff가 아님- (3/5) 의 리소스/노드셀렉터 항목도 그대로 적용됨
여기까지 맞으면 GitHub push → ECR → EKS 워커 Pod 까지 한 줄로 흐르는 거예요.
⚠️
ImagePullBackOff가 뜨면 거의 항상 노드 IAM 의AmazonEC2ContainerRegistryReadOnly가 안 붙어있는 거예요. 4번 섹션으로 다시.
8. 정리 — 시리즈를 닫으며
5편을 통과하면서 Airflow on K8s 의 풀스택이 한 번 그려졌어요.
| 편 | 핵심 |
|---|---|
| (1/5) 개요 | 큰 그림, KubernetesExecutor 가 워커 Pod 를 어떻게 다루는가 |
| (2/5) 설치 | Helm 차트 한 방으로 7~8 개 K8s 오브젝트 묶음 깔기 |
| (3/5) 워커 이미지 | 커스텀 이미지 빌드 + pod_template_file 로 워커 Pod 스펙 결정 |
| (4/5) 운영 | git-sync, 로그 영속화, 모니터링, 스케일, 장애 패턴 |
| (5/5) AWS 응용 (지금 글) | EKS + ECR + GitHub Actions 로 이미지 흐름 자동화 |
다음 단계로 자연스럽게 이어지는 주제들도 적어둘게요.
- IRSA (IAM Roles for Service Accounts) — Airflow 의 ServiceAccount 에 IAM Role 을 매핑해서 S3/RDS/Secrets Manager 등 AWS 리소스를 키 없이 접근. (4/5) 의 remote logging 을 IAM 키 없이 풀어내는 가장 깔끔한 방식.
- RDS Postgres + pgbouncer — 메타데이터 DB 를 EKS 밖으로 빼고 connection pool 까지 같이.
- ALB Ingress / 사내 SSO — Airflow UI 노출과 인증.
일단 오늘은 여기까지…..
시리즈 끝까지 따라와주셔서 고맙습니다. 다음엔 위 IRSA 패턴으로 다시 돌아올게요.
← 이전 글: (4/5) Airflow on K8s 운영 — git-sync · 로그 영속화 · 모니터링 · 스케일