4 분 소요

Airflow를 운영하다 보면 언젠가 한 번은 같은 고민을 하게 됩니다. Connection 이나 Variable 을 UI에 직접 넣고 관리해도 되나, 아니면 외부 비밀 저장소로 빼야 하나 같은 고민입니다.

저는 결국 MWAA + AWS Secrets Manager 조합으로 정리하는 쪽을 택했습니다. 이유는 단순했습니다. 운영 중인 Airflow에서 credential 과 설정값을 UI나 메타데이터 DB에 흩어놓고 관리하는 방식은 점점 불편해졌기 때문입니다.

특히 아래 문제가 계속 걸렸습니다.

  • 어떤 값이 실제 운영 기준인지 헷갈립니다
  • 환경별 분리가 깔끔하지 않습니다
  • 민감한 값 변경 이력을 추적하기 어렵습니다
  • DAG 코드에서는 단순히 conn_id 만 쓰고 싶은데, 실제 credential 관리는 별도로 하고 싶습니다

결국 Airflow는 참조만 하고, 실제 비밀값은 Secrets Manager 에서 읽도록 정리하는 것이 가장 덜 헷갈렸습니다.

왜 Secrets Manager 로 빼고 싶었는가

현재 저희 회사에서 운영 중인 구조를 보면, MWAA는 오케스트레이션 역할에 더 가깝고 실제 실행은 외부 서비스나 별도 실행 환경으로 많이 나갑니다. 이런 구조일수록 Airflow 내부에 많은 credential 을 쌓아두기보다, 외부 비밀 저장소를 기준으로 가져가는 편이 운영이 단순해집니다.

구성도를 먼저 보면 전체 흐름은 아래와 같습니다.

graph LR
    D["DAG 코드"] --> C["Airflow conn_id / Variable key"]
    C --> M["MWAA Secrets Backend"]
    M --> S["AWS Secrets Manager"]
    S --> V["실제 비밀값 반환"]

이렇게 정리하면 DAG 코드에서는 conn_idVariable.get() 만 그대로 쓰고, 실제 값은 Secrets Manager 가 공급하게 됩니다.

MWAA에서 먼저 해야 하는 설정

MWAA에서는 Airflow configuration options 에 secrets backend 를 붙여야 합니다.

핵심 설정은 아래 형태입니다.

secrets.backend = airflow.providers.amazon.aws.secrets.secrets_manager.SecretsManagerBackend
secrets.backend_kwargs = {
  "connections_prefix": "data/prod/airflow/connections",
  "variables_prefix": "data/prod/airflow/variables",
  "role_arn": "arn:aws:iam::000000000000:role/assume-role-airflow-secret-manager"
}

여기서 중요한 점은 backend_kwargs 가 Python dict 처럼 보여도 실제로는 JSON 문자열로 파싱 된다는 점입니다. Airflow Amazon provider 문서에도 이 값들이 JSON 으로 파싱된다고 나와 있습니다. 그래서 값 형식을 대충 넣으면 의도와 다르게 읽힐 수 있습니다.
출처:

즉 “대충 dict처럼 넣으면 되겠지”로 접근하면 생각보다 잘 안 맞습니다.

prefix 를 왜 나눴는가

저는 connections_prefixvariables_prefix 를 분리해서 두는 방식을 선호합니다.

예를 들면 아래처럼 가져갈 수 있습니다.

data/prod/airflow/connections/<conn_id>
data/prod/airflow/variables/<variable_key>

이렇게 해두면 장점이 분명합니다.

  • connection 과 variable 이 명확히 분리됩니다
  • 환경별 prefix 분리가 쉽습니다
  • 검색과 권한 관리가 조금 더 직관적입니다

정리된 경로 규칙이 없으면 나중에는 secret name 이 사실상 문서 역할까지 해야 해서 금방 지저분해집니다.

Connection 은 어떤 형식으로 넣어야 하나

Airflow 공식 문서를 보면 Connection 값은 URI 형식 또는 JSON 형식으로 표현할 수 있습니다. Secrets Manager backend 도 이 방식을 그대로 따릅니다.
출처: Managing Connections

실무에서는 JSON 형식이 보통 더 관리하기 편했습니다. 특히 extra 값을 많이 쓰는 provider connection 은 JSON 쪽이 덜 괴롭습니다.

기본 형식은 아래와 같습니다.

{
  "conn_type": "my-conn-type",
  "login": "my-login",
  "password": "my-password",
  "host": "my-host",
  "port": 1234,
  "schema": "my-schema",
  "extra": {
    "param1": "val1",
    "param2": "val2"
  }
}

이 값 자체를 Secrets Manager 의 SecretString 으로 넣어두면 됩니다.

구성도를 보면 아래와 같습니다.

graph TD
    A["Secret name
data/prod/airflow/connections/my_conn"] --> B["SecretString(JSON)"]
    B --> C["Airflow가 conn_id=my_conn 조회"]
    C --> D["Connection 객체로 변환"]

Google Cloud connection 은 extra 처리에서 자주 막힙니다

Google Cloud connection 은 특히 extra 처리가 번거로운 편입니다. 문서상 필요한 정보 대부분이 extra 안으로 들어갑니다.

예를 들면 이런 형태입니다.

{
  "conn_type": "google_cloud_platform",
  "extra": "{\"project\": \"my-gcp-project\", \"keyfile_dict\": \"{\\\"type\\\": \\\"service_account\\\", ...}\", \"num_retries\": 5, \"scope\": \"https://www.googleapis.com/auth/cloud-platform,https://www.googleapis.com/auth/bigquery\"}"
}

여기서 자주 쓰는 항목은 아래 정도입니다.

  • project
  • key_path
  • keyfile_dict
  • key_secret_name
  • key_secret_project_id
  • scope
  • num_retries

이 부분이 번거로운 이유는 extra 안에 다시 JSON 성격의 값이 들어가기 때문입니다. 특히 keyfile_dict 를 문자열로 넣는 순간 escape 처리가 꼬이기 쉽습니다.

실무에서는 이 지점에서 자주 문제가 납니다.

  • 겉보기에는 JSON 처럼 보이는데 실제로는 string escape 가 깨져 있습니다
  • extra 에 dict 를 넣었다고 생각했는데 실제로는 문자열 하나로 들어갑니다
  • 콘솔에서 복붙한 값이 줄바꿈이나 quote 처리 때문에 깨집니다

그래서 이 경우는 손으로 입력하는 것보다, 아예 json.dumps() 결과를 기준으로 넣는 편이 훨씬 안전했습니다.

Variable 은 더 단순합니다

Variable 쪽은 상대적으로 단순합니다. prefix 만 맞춰 두면 됩니다.

예를 들어 아래처럼 넣으면,

data/prod/airflow/variables/my_config_key

Airflow 코드에서는 평소처럼 사용할 수 있습니다.

from airflow.models import Variable

value = Variable.get("my_config_key")

DAG 코드 입장에서는 Secret Manager 를 직접 의식하지 않아도 되는 것이 장점입니다.

운영하면서 느낀 장점

실제로 붙여놓고 나면 체감 장점은 꽤 분명합니다.

1. DAG 코드가 깨끗해집니다

코드에서는 conn_idVariable.get() 만 쓰면 됩니다. credential 위치나 값 자체를 DAG가 알 필요가 없습니다.

2. 환경 분리가 쉬워집니다

dev, prod prefix 만 나눠도 비밀값의 경계를 분명하게 가져갈 수 있습니다.

3. 비밀 관리 주체가 분리됩니다

Airflow를 운영하는 사람과 credential 자체를 관리하는 사람의 책임을 나누기 쉬워집니다.

4. UI 의존도가 줄어듭니다

운영값을 UI에서 수동으로 넣고 수정하는 흐름이 줄어듭니다. 이건 생각보다 큽니다.

운영하면서 조심할 점

좋은 점만 있는 것은 아닙니다. 붙이고 나서 주의할 지점도 분명합니다.

1. IAM 권한이 먼저 맞아야 합니다

MWAA가 Secrets Manager 를 읽을 수 있어야 하고, 필요하면 role_arn 으로 assume role 도 가능해야 합니다. 이 부분이 안 맞으면 Airflow 코드가 틀린 것이 아니라 권한 때문에 못 읽는 상황이 자주 생깁니다.

2. prefix 와 secret name 규칙이 정확해야 합니다

코드는 conn_id="my_conn" 으로 찾는데 실제 secret name 은 다른 규칙으로 만들면 당연히 못 찾습니다. 이런 종류의 문제는 사소해 보여도 디버깅 시간을 꽤 먹습니다.

3. JSON 과 string 을 구분해서 넣어야 합니다

특히 backend_kwargsextra 는 JSON parsing 이 한 번 더 들어가는 지점이라 실수가 잦습니다. 여기서는 손 감각보다 형식 검증이 더 중요합니다.

데이터 엔지니어 관점에서의 정리

저는 Airflow에서 Secrets Manager 연동을 단순한 보안 기능으로만 보지는 않습니다. 이건 운영 책임을 어디에 둘 것인가에 대한 구조 정리에도 가깝다고 봅니다.

Airflow는 orchestration 에 집중하고,

  • connection 참조는 conn_id 로 단순화하고
  • 실제 비밀값은 외부 secret backend 에 두고
  • 환경별 분리는 prefix 와 IAM 으로 관리하는 구조가

결국 더 오래 가는 운영 방식에 가깝습니다.

특히 MWAA처럼 관리형 환경을 쓰는 경우에는 더 그렇습니다. Airflow 내부를 많이 만지기보다, 설정과 외부 secret storage 를 기준으로 정리하는 쪽이 훨씬 덜 불안합니다.

정리

이번 정리는 결국 하나로 요약됩니다.

Airflow에서 credential 을 직접 품고 있으려 하지 말고, MWAA에는 Secrets Manager 를 붙여서 참조와 저장을 분리 하는 편이 낫습니다.

설정 자체는 간단해 보이지만, 실제로는 아래 세 가지가 핵심입니다.

  • secrets.backendbackend_kwargs 를 정확히 넣기
  • connection / variable prefix 규칙 정하기
  • JSON 과 escape 처리를 정확히 하기

겉으로는 설정 두 줄처럼 보여도, 운영 관점에서는 꽤 큰 차이를 만드는 정리였습니다.

댓글 남기기

스팸 방지를 위해 짧은 시간에 반복 등록은 제한됩니다.

댓글 목록

관리자 보기