dbt Projects on Snowflake에서 SQL 환경 변수 사용하기

dbt Projects on Snowflake에서 SQL 환경 변수 사용하기

환경 변수는 dbt Core에 오랫동안 있었지만, 규모 있게 관리하려면 항상 늘어나는 엔지니어 팀에서 .env 파일을 이리저리 엮어야 했어요. 이 파일들은 개별 머신에 존재하고, 동기화가 어긋나며, 검토하거나 감사할 수 없어요. dbt Projects on Snowflake의 env.yml은 SQL in YAML 기능과 Snowflake 시크릿(secrets) 지원을 함께 제공하는 단일 Git 버전 관리 파일이에요. 관리자가 개발자별 스키마, 동적 런타임 값, 시크릿, CI/CD 구성을 한곳에서 관리할 수 있게 해주며, dbt Core만으로는 할 수 없는 워크플로를 열어줘요.

출처: Snowflake 문서

본문

이 가이드가 다루는 내용:

  • 개념(Concepts): env.yml 파일이 어떻게 작동하는지, env:와 secrets: 섹션, 환경과 값 우선순위, 이름 지정과 대소문자 규칙.
  • 관리자 설정(Admin setup): Snowflake 시크릿, 네트워크 규칙, 외부 접근 통합을 구성해 비공개 Git 패키지(공유 매크로, 모노레포의 하위 프로젝트, 또는 다른 팀의 dbt 프로젝트)를 가져오는 방법. 이것이 dbt Core 수준에서 교차 프로젝트 참조가 동작하는 방식이에요.
  • 환경 변수 사용하기: 하나 이상의 환경(예: dev, prod)을 정의하는 env.yml 파일 작성, 이를 읽도록 dbt_projects_profiles.yml 또는 profiles.yml 구성, 이를 소비하는 모델 작성, 그리고 Workspaces와 dbt 프로젝트 객체에서 SQL로 프로젝트 실행.
  • Snowflake CLI 사용하기: CI/CD 워크플로에 환경 변수 연결하기.
  • 관측성(Observability): 각 실행이 어떤 환경과 변수 재정의를 사용했는지 보기.
  • 참조(Reference): 지원되는 컨텍스트 함수와 Jinja 헬퍼, 환경 선택과 값 우선순위 표, 이름 지정 규칙.

관리자는 공유 빌딩 블록(Snowflake 시크릿, 네트워크 규칙, 외부 접근 통합)을 관리자 설정에서 구성해요. 데이터 엔지니어는 프로젝트 파일(env.yml, dbt_projects_profiles.yml 또는 profiles.yml, packages.yml, 모델)을 작성하고 프로젝트를 실행해요.

env.yml 파일은 Workspaces, 배포된 dbt 프로젝트 객체(SQL과 Snowflake CLI를 통해), 그리고 Snowflake 관리 모드의 CoCo Desktop 전반에서 작동해요. CoCo Desktop에서의 동작은 SQL 환경 변수와 비공개 Git 패키지 지원 문서를 참조하세요.

개념(Concepts)

env.yml 파일이란 무엇인가?

env.yml 파일은 dbt 프로젝트의 환경 변수와 시크릿을 정의하는 프로젝트 수준 구성 파일이에요. dbt Core의 .env 파일과 다른 점 몇 가지가 있어요:

  • dbt Core 또는 dbt Fusion 실행이 시작되기 전에 실행됨: 프로젝트를 실행하면 Snowflake가 env.yml 파일을 먼저 해결하고 결과 환경 변수를 실행에 주입해요. 그런 다음에야 dbt Core가 시작돼요. 그래서 값이 라이브 SQL(예: 현재 타임스탬프)과 Snowflake 시크릿에서 올 수 있어요. 모델이 실행되기 전인 실행 시작 시점에 계산되기 때문이에요.
  • 프로필 파일이 아니라 Snowflake 실행 컨텍스트를 사용함: CURRENT_ROLE(), CURRENT_USER(), CURRENT_WAREHOUSE() 같은 컨텍스트 함수는 dbt_projects_profiles.yml 또는 profiles.yml에 정의된 역할이 아니라, EXECUTE DBT PROJECT를 실행하는 외부 세션의 역할·사용자·웨어하우스(Workspaces 세션, Snowflake 태스크, 또는 Snowflake CLI 호출 여부)를 기준으로 해결돼요. 모든 Snowflake 시크릿 해결과 SELECT 쿼리에도 동일하게 적용돼요.
  • dbt 프로젝트의 루트에 위치함: dbt_project.yml과 같은 폴더에 env.yml을 두세요. 실행 중 Workspaces와 dbt 프로젝트 객체는 거기서 찾을 것으로 기대해요.
  • Git 버전 관리되고 감사 가능함: 개발자 머신에 있는 .env 파일과 달리 env.yml은 버전 관리돼요. 관리자가 팀이 필요한 환경 변수 집합을 조정하고 변경을 커밋해 팀 전체가 이 통합 구성의 혜택을 받게 할 수 있어요.
  • 유연함: 개발자는 Workspaces 내 또는 dbt 프로젝트 객체 실행 중에 개별 환경 변수를 재정의할 수 있어요.
  • 여러 환경 지원: 하나의 파일로 dev, prod, staging, 그리고 필요한 다른 환경을 정의할 수 있어요.

파일에는 2MB 크기 제한이 있으며, 대략 12,000줄을 지원해요.

파일 구조

env_config: # 최상위 키. 필수.
  default_environment: my_env_dev # 실행 시 지정되지 않았을 때 사용되는 환경.
  environments: # 사용 가능한 환경 목록.
    - name: my_env_dev # 각 환경에 이름을 지정합니다.
      secrets: # 마스킹된 환경 변수로 주입되는 Snowflake 시크릿.
        - snowflake_secret: my_db.my_schema.my_token
          env_var_name: DBT_ENV_SECRET_GIT_CREDENTIAL
      env: # 일반 환경 변수 (텍스트 또는 VARCHAR를 반환하는 SQL).
        DBT_CURRENT_ROLE: "{{ select CURRENT_ROLE() }}"
        DBT_HARDCODED: VALUE

env:와 secrets: 섹션

각 환경 항목에는 서로 다른 목적을 가진 두 개의 선택 섹션이 있어요.

env: 값은 dbt 실행을 동적으로 구성해요. 환경 변수로 개발자별 스키마를 만들 수 있어요. 이를 통해 큰 엔지니어링 팀이 개발 중 충돌을 피할 수 있어요. 또한 Snowflake의 SQL in YAML 기능은 오케스트레이터 없이 런타임에 Airflow 스타일 데이터 윈도우 타임스탬프를 계산하거나, CI/CD 파이프라인을 연결해 풀 리퀘스트 번호로 명명된 데이터베이스를 동적으로 생성하고 그에 대해 실행할 수 있어요. dbt 모델의 코드를 한 줄도 바꾸지 않고요.

값은 일반 텍스트이거나, VARCHAR 타입의 한 행과 한 컬럼을 반환하는 SQL 쿼리(SELECT column FROM your_control_table 같은 전체 테이블 쿼리와 SELECT * FROM TABLE(...)로 호출하는 저장 프로시저 포함)일 수 있어요. Jinja가 올바르게 파싱하도록 SQL 쿼리를 큰따옴표로 감싸세요. 하루 시작·하루 끝 시간 윈도우 예시는 env.yml 파일 작성 문서를, 저장 프로시저 예시는 env.yml에서 저장 프로시저 호출 문서를 참조하세요.

secrets: 값은 Snowflake 관리 자격 증명을 실행으로 가져와요. 주요 용도는 dbt deps 호출 인증이에요: 다른 팀 저장소에서 비공개 Git 패키지를 가져오거나, 팀이 모노레포를 쓴다면 같은 Git 저장소 안의 하위 프로젝트를 참조하는 방식이에요. 주입되면 시크릿 값은 DBT_ENV_SECRET_ 접두사 변수로 사용 가능해요. 이는 dbt Core 모범 사례를 사용해 모든 로그에서 값을 ****로 마스킹해요. 지원되는 컨텍스트 변수는 {{current_user}}, {{current_account}}, {{current_account_name}}, {{current_organization_name}}이에요. 각 엔지니어에게 개인 시크릿을 제공하려면 snowflake_secret 참조 안에서 {{current_user}}를 사용할 것을 권장해요. 시크릿은 비공개 Git 저장소에 대한 dbt deps 호출을 인증하기 위해 packages.yml에서만 사용할 것을 권장해요. 모델, 매크로, 또는 다른 dbt 아티팩트에서 DBT_ENV_SECRET_ 변수를 참조하는 것은 피하세요.

env.yml 파일 어디에서도 매크로는 허용되지 않아요. 표준 dbt Core 환경 변수는 매크로가 사용할 수 있기 전에 해결되어야 하므로, env.yml 안에서 매크로를 허용하면 순환 의존성(circular dependency)이 생길 수 있어요.

환경과 기본 환경

environments: 아래의 각 항목은 변수 키와 그 값의 집합을 정의하는 명명된 환경이에요. 두 가지 별개의 우선순위가 있다는 점을 구분해야 해요.

실행마다 정확히 하나의 환경이 활성화돼요. 가장 높은 우선순위부터 순서대로 선택돼요(환경 선택 참조):

  1. EXECUTE DBT PROJECT의 ENVIRONMENT = '...' (가장 높음).
  2. dbt 프로젝트 객체에 설정된 DEFAULT_ENVIRONMENT.
  3. env.yml의 default_environment.

Workspaces에서는 실행을 시작하기 전에 실행 패널의 환경 선택기로 환경을 선택해요. Workspaces에서 프로젝트 실행 문서를 참조하세요.

활성 환경이 선택된 후, 개별 변수 값은 가장 높은 우선순위부터 순서대로 해결돼요(값 우선순위 참조):

  1. --env-vars / EXECUTE ... ENV_VARS (가장 높음). 선택된 환경에 병합되고 최종 우선순위를 가져요.
  2. 셸 환경 변수. --use-shell-env-vars를 사용할 때만. DBT_ 접두사가 붙은 모든 셸 변수(DBT_ENV_SECRET_* 변수 제외)가 사용되며, env.yml에 정의되지 않은 것도 포함돼요.
  3. env.yml의 선택된 default_environment. 파일의 기본 구성. 최저 우선순위예요.

환경 없이 실행하려면 예약된 이름 NO_ENV를 사용하세요. 환경 없이 실행(NO_ENV) 문서를 참조하세요.

이름 지정과 대소문자 규칙

이 규칙들은 모든 실행에 적용돼요. 어기면 실행이 실패해요.

  • env:의 모든 키와 재정의는 DBT_로 접두사가 붙어야 해요(DBT_ENV_CUSTOM_ENV_와 DBT_ENV_SECRET_ 포함).
  • 모든 키는 UPPERCASE여야 해요.
  • secrets: 섹션의 모든 키는 DBT_ENV_SECRET_ 접두사가 붙어야 해요. dbt는 모든 로그와 오류 메시지에서 DBT_ENV_SECRET_ 변수 값을 ****로 마스킹해요.
  • 키 이름(왼쪽)은 일반 텍스트여야 해요. SQL일 수 없어요.
  • 환경 이름은 대소문자를 구분하며 영어 문자, 숫자, 밑줄을 최대 256자까지 포함할 수 있어요.

관리자 설정(Admin setup)

dbt 프로젝트가 비공개 Git 패키지를 가져오지 않는다면 이 섹션은 건너뛰어도 돼요. 이 섹션은 두 가지 일반적인 시나리오를 다뤄요:

  • 모노레포 패턴: 팀이 단일 비공개 Git 저장소에 여러 dbt 프로젝트를 유지해요. packages.yml의 subdirectory: 키를 사용해 그 저장소의 하위 폴더를 패키지로 가져와요.
  • 별도 저장소 패턴: 다른 팀이 의존하는 dbt 프로젝트가 있는 비공개 Git 저장소를 소유해요. 그 저장소 전체(또는 그 하위 폴더)를 패키지로 가져와요.

두 패턴 모두 같은 메커니즘을 사용해요: Git 접근 토큰을 담는 Snowflake 시크릿, Git 호스트로의 트래픽을 허용하는 네트워크 규칙, 그리고 이들을 묶는 외부 접근 통합. 설정이 끝나면 dbt deps가 Git 공급자로 인증하고 패키지를 가져와요. 그런 다음 dbt Core의 두 인자 ref()로 그 모델을 참조해요. 이것이 dbt Core 수준에서 교차 프로젝트 참조가 동작하는 방식이에요.

packages.yml의 최종 결과는 다음과 같아요:

packages:
  # ...other packages
  - git: "https://{{env_...')}}@github.com/my-github-account/jaffle-shop-demo.git"
    subdirectory: "jaffle-shop"
    revision: a47561234f73937cf58589ca2247475b40341237

나머지 이 섹션은 여기까지 오는 데 필요한 각 요소를 안내해요.

💡 환경에 secrets: 섹션을 포함하면 명령을 실행할 때마다 외부 접근 통합을 선택해야 해요. dbt deps(또는 시크릿이 필요한 명령)를 실행하지 않는다면 env.yml의 secrets: 블록을 주석 처리해 이 요구 사항을 건너뛰세요.

Snowflake 시크릿 생성

Git 접근 토큰 같은 민감한 값은 하드코딩하지 말고 Snowflake 시크릿 객체로 저장해요. env.yml에서 시크릿을 참조하면 Snowflake가 런타임에 이를 마스킹된 환경 변수로 주입해요.

가장 간단한 방법은 dbt 프로젝트가 모든 환경에서 참조하는 단일 팀 전체 시크릿이에요. 이 시크릿용 Git 접근 토큰을 만들 때 읽기 전용 접근으로 범위를 지정해요(예: GitHub의 "Contents: Read-only" 저장소 권한). 이는 팀원들이 이미 저장소 자체에 갖고 있는 접근과 일치하므로, 공유 토큰이 개별적으로 가진 것보다 더 넓은 접근을 부여하지 않아요.

먼저 Git 공급자에서 토큰을 만들어요. GitHub는 개인 접근 토큰 생성 문서를 따르세요. 그런 다음 값을 Snowflake 시크릿에 저장하고 팀 공유 역할에 READ를 부여해요:

CREATE OR REPLACE SECRET tasty_bytes_dbt_db.integrations.tb_dbt_git_secret
  TYPE = GENERIC_STRING
  SECRET_STRING = 'ghp_...<your read-only token>';

GRANT READ ON SECRET tasty_bytes_dbt_db.integrations.tb_dbt_git_secret TO ROLE data_engineer;

더 세분화된 제어를 위해 토큰이 독립적으로 해지될 수 있도록 개발자별 개별 시크릿을 만들어요. env.yml은 런타임에 {{current_user}}를 해결하므로 각 엔지니어의 시크릿을 사용자 이름으로 명명해요:

CREATE OR REPLACE SECRET tasty_bytes_dbt_db.integrations.jdoe_personal_git_secret
  TYPE = GENERIC_STRING
  SECRET_STRING = 'ghp_...<your token>';

GRANT READ ON SECRET tasty_bytes_dbt_db.integrations.jdoe_personal_git_secret TO USER jdoe;

사용자에게 직접 부여된 권한은 그 사용자가 모든 보조 역할(secondary roles)을 활성화했을 때만 유효해요. 자세한 내용은 GRANT <privileges> … TO USER 문서를 참조하세요.

CI/CD 파이프라인의 경우 서비스 계정용 별도 시크릿을 만들고 서비스 계정 사용자에게 READ를 부여해요(예: CI/CD 튜토리얼에서 만든 github_actions_service_user). 저장소에 모든 의존성이 체크인된 완전한 dbt_packages 폴더가 이미 있다면 이 단계는 건너뛰어요. 그 경우 dbt deps를 실행하지 않고 프로젝트 객체를 직접 배포할 수 있어요.

CREATE OR REPLACE SECRET tasty_bytes_dbt_db.integrations.tb_dbt_github_actions_git_secret
  TYPE = GENERIC_STRING
  SECRET_STRING = 'ghp_...<your token>';

GRANT READ ON SECRET tasty_bytes_dbt_db.integrations.tb_dbt_github_actions_git_secret TO USER github_actions_service_user;

이미 시크릿 객체가 있고 값을 순환 교체(rotate)해야 한다면, 다른 속성을 실수로 재설정하지 않도록 CREATE OR REPLACE 대신 ALTER SECRET을 사용하세요.

ALTER SECRET tasty_bytes_dbt_db.integrations.tb_dbt_github_actions_git_secret
  SET SECRET_STRING = 'ghp_...<new token>';

Workspaces를 비공개 Git 저장소에 연결한다면 dbt 프로젝트를 배포할 때 사용하는 것과 같은 tb_dbt_git_api_integration Git API 통합에도 시크릿을 인가하세요.

CREATE OR REPLACE API INTEGRATION tb_dbt_git_api_integration
  API_PROVIDER = git_https_api
  API_ALLOWED_PREFIXES = ('https://github.com/my-github-account')
  ALLOWED_AUTHENTICATION_SECRETS = (
    tasty_bytes_dbt_db.integrations.tb_dbt_git_secret,
    tasty_bytes_dbt_db.integrations.tb_dbt_github_actions_git_secret
  )
  ENABLED = TRUE;

실행 역할은 실행에서 이를 사용하려면 시크릿에 READ 또는 OWNERSHIP이 있어야 해요.

비공개 Git 트래픽 허용 (네트워크 규칙 업데이트)

비공개 Git 패키지를 가져오려면 관리자가 네트워크 규칙을 업데이트해 Git 호스트로의 트래픽을 허용해요. 비공개 Git 패키지를 사용할 것이라면 github.com(또는 Git 호스트)을 허용 목록에 추가해야 해요.

처음부터 시작한다면 전체 허용 목록으로 네트워크 규칙을 만들어요.

CREATE OR REPLACE NETWORK RULE dbt_network_rule
  MODE = EGRESS
  TYPE = HOST_PORT
  -- dbt deps에 필요한 최소 허용 목록
  VALUE_LIST = (
    'hub.getdbt.com',
    'codeload.github.com',
    'github.com',
          -- GitHub
    -- 'gitlab.com',       -- GitLab
    -- 'dev.azure.com',    -- Azure DevOps
    -- 'bitbucket.org'     -- Bitbucket
  );

일부 조직은 이 공급자들의 자체 호스팅 인스턴스를 운영해요. 예를 들어 GitLab self-managed, Bitbucket Data Center, Azure DevOps Server, 또는 GitHub Enterprise Server가 그렇죠. 팀이 그렇다면 주석 처리된 호스트를 인스턴스의 커스텀 도메인(예: 'gitlab.mycompany.com')으로 바꾸세요.

dbt용 네트워크 규칙이 이미 있다면 ALTER로 기존 VALUE_LIST에 Git 공급자(예: github.com)를 추가해요.

ALTER NETWORK RULE dbt_network_rule SET
  VALUE_LIST = ('hub.getdbt.com', 'codeload.github.com', 'github.com');
네트워크 규칙은 경로를 지원하지 않아요

호스트만 추가하세요. HOST_PORT 네트워크 규칙 항목은 선택적 포트와 함께 유효한 도메인으로 해결되어야 해요. URL 경로를 포함할 수 없으므로 github.com/my-repo 같은 항목은 지원되지 않아요.

-- 이것은 동작하지 않습니다. HOST_PORT 네트워크 규칙은 /my-repo 같은 경로를 지원하지 않습니다.
-- ALTER NETWORK RULE dbt_network_rule SET
--   VALUE_LIST = ('hub.getdbt.com', 'codeload.github.com', 'github.com/my-repo');

유효한 값에 대한 전체 규칙은 CREATE NETWORK RULE 문서를 참조하세요.

외부 접근 통합 업데이트

다음으로 관리자가 외부 접근 통합(EAI)을 업데이트해 엔지니어가 관리자가 매번 EAI를 편집하지 않아도 env.yml 파일에서 시크릿을 참조할 수 있게 해요. ALLOWED_AUTHENTICATION_SECRETS를 all로 설정해요.

EAI가 현재 허용하는 것을 보려면 먼저 describe해요.

DESCRIBE EXTERNAL ACCESS INTEGRATION dbt_ext_access;

처음부터 시작한다면 ALLOWED_AUTHENTICATION_SECRETS=all로 EAI를 만들어요.

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION dbt_ext_access
  ALLOWED_NETWORK_RULES = (dbt_network_rule)
  ALLOWED_AUTHENTICATION_SECRETS = all
  -- 시간이 지나며 저장소나 접근 토큰이 바뀌거나 많은 개인 접근 토큰을 활성화할 것으로 예상된다면 권장.
  -- ALLOWED_AUTHENTICATION_SECRETS = (tasty_bytes_dbt_db.integrations.tb_dbt_git_secret) -- 조직에 저장소 접근용 단일 관리자 시크릿이 있거나 팀의 각 시크릿을 나열하려면 이것을 사용.
  ENABLED = TRUE;

통합에 대한 USAGE를 필요로 하는 역할에 부여해요.

GRANT USAGE ON INTEGRATION dbt_ext_access TO ROLE accountadmin;

GRANT USAGE ON INTEGRATION dbt_ext_access TO ROLE data_engineer;

이미 EAI가 있다면 ALTER로 모든 시크릿을 허용하도록 전환해요. 되돌리려면 UNSET을 사용해요.

ALTER EXTERNAL ACCESS INTEGRATION dbt_ext_access
  SET ALLOWED_AUTHENTICATION_SECRETS = all;

-- ALTER EXTERNAL ACCESS INTEGRATION dbt_ext_access UNSET ALLOWED_AUTHENTICATION_SECRETS;

처음 EAI를 만들 때 dbt_ext_access에 대한 USAGE를 이미 부여했다면 여기서 다시 부여할 필요 없어요. 통합의 ALLOWED_AUTHENTICATION_SECRETS 설정만 업데이트하는 거예요.

왜 ALLOWED_AUTHENTICATION_SECRETS = all이 여전히 안전한가

ALLOWED_AUTHENTICATION_SECRETS=all 설정은 안전하고 권장되는 방식이에요. 모든 future 스키마에 권한을 부여하는 것과 비슷해요: 접근 제어를 약화시키지 않으면서 반복적인 관리 병목을 제거해요. 안전하게 유지되는 이유는 다음과 같아요:

  • 일회성 조정: all로 설정하면 팀이 시크릿을 추가하거나 순환 교체할 때 EAI를 다시 바꿀 필요가 없어요. 아래 대안(개별 시크릿 나열)은 추가할 때마다 ALTER가 필요해요.
  • 여전히 READ 또는 OWNERSHIP이 필요함: EAI에서 시크릿을 허용해도 접근이 부여되지는 않아요. 실행 역할은 지정된 시크릿 객체에 READ 권한(또는 OWNERSHIP)을 여전히 보유해야 해요. 사용자는 부여받은 시크릿만 사용할 수 있어요.
  • 네트워크 규칙이 여전히 시크릿이 갈 수 있는 곳을 제어함: all은 어떤 시크릿이든 사용될 수 있다는 뜻이지만, 시크릿은 네트워크 규칙에 나열된 호스트로만 전송될 수 있어요. ALLOWED_AUTHENTICATION_SECRETS가 무엇으로 설정됐든 나열되지 않은 호스트에 도달하려는 시도는 거부돼요.
  • dbt가 시크릿 값을 마스킹함: 모든 시크릿이 DBT_ENV_SECRET_ 변수를 통해 참조되므로 dbt는 모든 로그와 오류 메시지에서 값을 ****로 대체해요.

이점은 엔지니어가 env.yml에서 자체 시크릿을 추가·관리하며 자유롭게 작업할 수 있고, 관리자는 시크릿 객체에 대한 READ 부여 프로비저닝을 통해 제어를 유지한다는 것이에요.

대안: 개별 시크릿 나열

모든 시크릿을 허용하지 않으려면 각각을 명시적으로 나열해요. EAI 자체가 프로젝트가 사용할 수 있는 정확한 시크릿을 제한하길 원할 때 사용해요.

CREATE OR REPLACE EXTERNAL ACCESS INTEGRATION dbt_ext_access
  ALLOWED_NETWORK_RULES = (dbt_network_rule)
  ALLOWED_AUTHENTICATION_SECRETS = (
    tasty_bytes_dbt_db.integrations.tb_dbt_git_secret,
    tasty_bytes_dbt_db.integrations.tb_dbt_github_actions_git_secret,
    tasty_bytes_dbt_db.integrations.jdoe_tb_dbt_git_secret,
    tasty_bytes_dbt_db.integrations.msmith_tb_dbt_git_secret
    -- 프로젝트가 참조하는 각 시크릿을 추가하세요.
  )
  ENABLED = TRUE;

dbt 프로젝트 객체가 EXTERNAL_ACCESS_INTEGRATIONS=('EAI_1','EAI_2')처럼 둘 이상의 EAI를 사용하고, 그중 하나라도 ALLOWED_AUTHENTICATION_SECRETS=all이면 선택된 환경에서 참조되는 모든 시크릿이 EAI의 시크릿 허용 목록을 통과해요. 허용 목록 통과가 접근 부여와 같은 것은 아니에요. 실행 역할은 시크릿 객체에 READ 또는 OWNERSHIP을 여전히 보유해야 사용할 수 있어요.

packages.yml을 업데이트해 비공개 Git 패키지 사용

네트워크 규칙과 EAI가 준비되면 packages.yml에서 비공개 Git 패키지를 참조해요. env.yml의 DBT_ENV_SECRET_ 변수로 Git 자격 증명을 안전하게 주입한 다음 dbt deps를 실행해 패키지를 가져와요.

packages:
  - package: dbt-labs/dbt_utils
    version: 1.1.1
  - package: Snowflake-Labs/dbt_semantic_view
    version: [">=1.0.0", "<2.0.0"]
  - git: "https://{{env_...')}}@github.com/my-github-account/jaffle-shop-demo.git"
    subdirectory: "jaffle-shop"
    revision: a47561234f73937cf58589ca2247475b40341237 # 저장소의 아무 커밋 URL에서 얻을 수 있습니다. 축약 커밋 ID는 작동하지 않습니다.
  - git: "https://{{env_...')}}@github.com/my-github-account/jaffle-shop-demo.git"
    subdirectory: "flower-shop"
    # 교차 프로젝트 의존성. 같은 저장소지만 다른 하위 폴더를 참조합니다.
    revision: b9831234f73937cf58589ca2247475b40341298

최신 main 브랜치(revision: main)를 사용하는 것도 지원되지만, dbt deps 호출 시 호환성을 깨는 변경을 도입할 수 있으므로 권장되지 않아요.

임포트된 패키지 참조

dbt deps가 패키지를 가져온 후 두 인자 ref()로 자체 프로젝트에서 그 모델을 참조해요: {{ ref('package_name','model_name') }}. 패키지 이름이 먼저 와요.

두 가지 알아둘 점:

  • 한 인자 대 두 인자: 한 인자 ref('model_name')는 자체 프로젝트의 모델로 해결돼요. 두 인자 ref('package_name', 'model_name')는 임포트된 패키지의 모델로 해결돼요. dbt는 다른 패키지의 모델을 참조할 때마다 두 인자 형태를 권장해요. 모델 이름이 둘 이상의 프로젝트에 존재해도 참조가 모호하지 않게 해주기 때문이에요.
  • 패키지 이름이 어디서 오는가: 첫 번째 인자는 dbt 프로젝트 이름이에요. 임포트된 패키지의 dbt_project.yml에 선언돼 있어요. Git 저장소 이름(jaffle-shop-demo)이나 subdirectory 값(jaffle-shop)이 아니에요. 이 패키지의 경우 그 프로젝트 이름은 jaffle_shop이에요.

아래 예시는 tasty_bytes 프로젝트의 모델이에요. 자체 모델 두 개(simple_customers, combined_bookings, 한 인자 ref())를 조인하고 임포트된 jaffle_shop 패키지의 모델(두 인자 ref())로 강화해요.

-- tasty_bytes 프로젝트의 모델입니다.
-- ref('simple_customers')와 ref('combined_bookings')는 이 프로젝트의 모델입니다.
-- ref('jaffle_shop', 'stg_customers')는 임포트된 jaffle_shop 패키지의 모델입니다.

SELECT
    A.ID,
    FIRST_NAME,
    LAST_NAME,
    birthdate,
    BOOKING_REFERENCE,
    HOTEL,
    BOOKING_DATE,
    COST,
    COST as cost_1,
    COST as cost_2,
    BOOKING_DATE as BOOKING_DATE_1,
    C.customer_id as jaffle_shop_customer_id
FROM {{ ref('simple_customers') }} A
JOIN {{ ref('combined_bookings') }} B
  ON A.ID = B.ID
LEFT JOIN {{ ref('jaffle_shop', 'stg_customers') }} C
  ON A.ID = C.customer_id

패키지 모델에서 선택하는 컬럼(여기서는 customer_id)은 그 패키지의 스키마에 따라 달라져요. 정확한 컬럼 이름은 임포트된 패키지의 모델을 확인하세요.

환경 변수 사용 시작하기

사전 요구 사항

시작하기 전에 다음이 있는지 확인하세요:

  • dbt Projects on Snowflake와 Workspaces에 대한 접근.
  • 프로젝트를 실행하는 데 사용할 수 있는 웨어하우스. 이 웨어하우스가 실행 시작 시 env.yml 파일을 실행해요.
  • env.yml에서 참조하는 모든 Snowflake 시크릿에 READ 권한 또는 OWNERSHIP이 있는 역할.
  • dbt 프로젝트(이 가이드는 tasty_bytes dbt 프로젝트를 사용하고 비공개 jaffle_shop 패키지를 의존성으로 임포트해요).
  • CI/CD 워크플로에 CLI를 사용한다면 Snowflake CLI 버전 3.21 이상.

프로젝트가 이미 환경 변수를 사용한다면

모델이나 profiles.yml에서 이미 env_var()를 호출한다면, env.yml에서 작동하도록 DBT_ 접두사 요구 사항을 충족시키기 위해 변수를 이름을 바꿔야 해요. 다음 단계를 복사해 붙여넣어 CoCo가 무거운 작업을 처리하도록 하는 것을 권장해요:

  1. 프로젝트에서 모든 env_var() 호출 스캔: CoCo에게 모델, 매크로, profiles.yml 전반에서 참조되는 모든 고유 환경 변수를 나열하라고 요청하세요.
  2. env.yml 파일에 변수 추가: env.yml 파일 작성 문서의 env.yml 구문을 사용하세요. 어느 것을 하드코딩할지 정하는 동안 값은 "" 자리표시자로 설정하세요.
  3. 각 변수를 DBT_ 접두사의 UPPERCASE로 이름 변경: 예를 들어 my_schema는 DBT_MY_SCHEMA가 돼요. 값은 그대로예요.
  4. 프로젝트 파일에서 참조 업데이트: CoCo에게 모델, 매크로, profiles.yml 전반의 모든 env_var('OLD_NAME') 호출을 한 번에 env_var('DBT_OLD_NAME')으로 바꾸라고 요청하세요.

env.yml 파일 작성

dbt 프로젝트 루트의 dbt_project.yml 옆에 env.yml이라는 파일을 만들어요. 아래 예시는 dev, staging, prod, prod_complex 네 환경을 정의해요. 기본 환경은 dev예요. dev와 staging 환경은 CURRENT_USER() 같은 메타데이터 함수를 사용해 각 엔지니어에게 격리된 스키마를 줘서 팀원들이 개발 중 충돌하지 않게 해요. 프로덕션 환경은 날짜·시간 함수로 런타임에 증분 데이터 윈도우를 계산해요.

env: 섹션은 해결 결과가 VARCHAR 타입의 단일 행과 한 컬럼이 되는 한 모든 SQL 쿼리를 지원해요. 컨트롤 테이블, dbt 실행 기록 테이블, 태스크 기록 테이블, 또는 설정한 다른 어떤 테이블이든 쿼리할 수 있어요.

env_config: # 최상위: YAML에 이름이 필요함 - 현재 "env_config:"
  default_environment: dev
  environments: # 사용 가능한 환경 목록
    - name: dev # 사용자가 환경 이름을 정의
      # 비공개 Git 패키지를 사용한다면 주석 해제. 위 "비공개 Git 패키지 임포트" 참조.
      # secrets:
      # - snowflake_secret: tasty_bytes_dbt_db.integrations.tb_dbt_git_secret  # 팀 공유 시크릿
      #   # DBT_ENV_SECRET_로 앞에 붙어야 함
      #   env_var_name: DBT_ENV_SECRET_GIT_TOKEN
      env:
        # 메타데이터 함수 지원, Jinja 파싱에는 따옴표 필요
        DBT_CURRENT_WH: "{{ select CURRENT_WAREHOUSE() }}"
        DBT_CURRENT_DB: tasty_bytes_dbt_db
        DBT_CURRENT_SCHEMA: "{{ select CURRENT_USER() }}"
        DBT_CURRENT_ROLE: "{{ select CURRENT_ROLE() }}"
        DBT_CURRENT_USER: "{{ select CURRENT_USER() }}"
        DBT_FEATURE_FLAG_X_Y_Z: VALUE
        DBT_DATA_INTERVAL_START: "2020-01-01 00:00:00"
        DBT_DATA_INTERVAL_END: "2099-12-31 23:59:59"
    - name: staging # 사용자가 환경 이름을 정의
      env:
        DBT_CURRENT_WH: tasty_bytes_dbt_wh
        DBT_CURRENT_DB: "{{ select CURRENT_USER() }}_tasty_bytes_dbt_db"
        DBT_CURRENT_SCHEMA: "{{ select CURRENT_USER() }}"
        DBT_CURRENT_ROLE: "{{ select CURRENT_ROLE() }}"
        DBT_CURRENT_USER: "{{ select CURRENT_USER() }}"
        DBT_DATA_INTERVAL_START: "2020-01-01 00:00:00"
        DBT_DATA_INTERVAL_END: "2099-12-31 23:59:59"
    - name: prod # 사용자가 환경 이름을 정의
      env:
        DBT_CURRENT_WH: tasty_bytes_dbt_wh
        DBT_CURRENT_DB: tasty_bytes_dbt_db
        DBT_CURRENT_SCHEMA: "{{ select CURRENT_USER() }}"
        DBT_CURRENT_ROLE: accountadmin
        # 하루 시작 쿼리, Jinja 파싱에는 따옴표 필요
        DBT_DATA_INTERVAL_START: "{{ select (DATE_TRUNC('DAY', CURRENT_TIMESTAMP()) - INTERVAL '1 DAY')::string }}"
        DBT_DATA_INTERVAL_END: "{{ select (DATE_TRUNC('DAY', CURRENT_TIMESTAMP()) - INTERVAL '1 SECOND')::string }}"
    - name: prod_complex # 사용자가 환경 이름을 정의
      env:
        DBT_CURRENT_WH: tasty_bytes_dbt_wh
        DBT_CURRENT_DB: tasty_bytes_dbt_db
        DBT_CURRENT_ROLE: accountadmin
        # 마지막 성공한 실행의 완료 시간
        DBT_DATA_INTERVAL_START: "{{ select COALESCE((SELECT COMPLETED_TIME::STRING FROM TABLE(SNOWFLAKE.INFORMATION_SCHEMA.TASK_HISTORY(TASK_NAME => 'RUN_TASTY_BYTES_DBT_OBJECT', RESULT_LIMIT => 100)) WHERE STATE = 'SUCCEEDED' ORDER BY COMPLETED_TIME DESC LIMIT 1), (DATE_TRUNC('DAY', CURRENT_TIMESTAMP()) - INTERVAL '1 DAY')::STRING) }}"
        # 현재 실행의 시작 시간
        DBT_DATA_INTERVAL_END: "{{ select CURRENT_TIMESTAMP()::STRING }}"

프로필 파일 구성

dbt_projects_profiles.yml 또는 profiles.yml 파일은 env_var()를 사용해 env.yml에 정의한 환경 변수를 읽어요. Jinja가 올바르게 파싱하도록 각 env_var() 호출을 큰따옴표로 감싸세요. 이 설정으로 같은 프로필 파일이 각 엔지니어에게 dev에서 다른 연결 target을 만들어내는데, 값이 주입된 환경에서 오기 때문이에요. 두 파일이 모두 있으면 Snowflake는 dbt_projects_profiles.yml을 사용해요.

tasty_bytes:
  target: dev
  outputs:
    dev:
      type: snowflake
      account: "not needed"
      user: "not needed"
      role: "{{ env_var('DBT_CURRENT_ROLE') }}"
      database: "{{ env_var('DBT_CURRENT_DB') }}"
      schema: "{{ env_var('DBT_CURRENT_SCHEMA') }}"
      warehouse: "{{ env_var('DBT_CURRENT_WH') }}"
      threads: 8

📌 Workspaces는 편집기에서 role과 warehouse 필드를 사전 검증해요. 그 함수를 감싸는 동적 표현식(예: "{{ select REPLACE(STRTOK(CURRENT_WAREHOUSE(),'_',1)) }}")은 "env.yml value contains Jinja that is not a supported Snowflake context function" 오류로 Workspaces 검증에 실패해요. 이 두 연결 필드를 공급하는 env.yml 값은 단순 컨텍스트 함수("{{ select CURRENT_ROLE() }}" 또는 "{{ select CURRENT_WAREHOUSE() }}")여야 해요.

database와 schema 필드에는 이 제한이 없어요. 문자열 조작, 테이블 쿼리, 저장 프로시저를 포함한 모든 지원 SQL을 받아들여요.

이 제한은 Workspaces에만 적용돼요. 배포된 dbt 프로젝트 객체(예: EXECUTE DBT PROJECT 또는 Snowflake CLI를 사용한 CI/CD)에서는 role과 warehouse도 복잡한 SQL을 받아들여요.

환경 변수를 소비하는 모델 작성

모델에서 env_var()를 사용해 주입된 값을 읽어요. 아래 예시는 두 업스트림 모델을 조인하고 주입된 시간 윈도우를 기준으로 필터링하므로, 같은 모델이 실행되는 환경에 따라 다른 날짜 범위를 처리해요.

SELECT
  a.BOOKING_DATE,
  a.HOTEL,
  a.COST,
  a."30_DAY_AVG_COST",
  a."DIFF_BTW_ACTUAL_AVG",
  b.count_bookings
FROM {{ ref('thirty_day_avg_cost') }} a
JOIN {{ ref('hotel_count_by_day') }} b
  ON a.BOOKING_DATE = b.BOOKING_DATE
  AND a.HOTEL = b.HOTEL
WHERE
  a.BOOKING_DATE >= '{{ env_var("DBT_DATA_INTERVAL_START") }}'::TIMESTAMP
  AND a.BOOKING_DATE <= '{{ env_var("DBT_DATA_INTERVAL_END") }}'::TIMESTAMP

Workspaces에서 프로젝트 실행

📌 env.yml에 secrets: 블록이 있으면 프로젝트를 실행하려면 명령을 실행할 때마다 외부 접근 통합을 선택해야 하고, 실행 역할은 실행에서 이를 사용하려면 시크릿에 READ 또는 OWNERSHIP이 있어야 해요.

Workspaces에서 선택된 환경에서 프로젝트를 실행해요. Snowflake가 env.yml을 먼저 해결해 환경 변수와 시크릿을 주입해요. CURRENT_WAREHOUSE()는 현재 세션을 사용하며 실행 중인 웨어하우스 없이 해결돼요. 해결된 값은 dbt_projects_profiles.yml 또는 profiles.yml로 흘러가고, 프로젝트는 파일이 지정한 웨어하우스에서 실행돼요.

실행 전에 실행 패널의 환경 선택기로 활성 환경을 선택해요. dbt 프로젝트 객체의 DEFAULT_ENVIRONMENT 또는 EXECUTE DBT PROJECT의 ENVIRONMENT로 환경을 설정하거나 재정의할 수도 있어요. dbt 프로젝트 객체 생성 및 실행 문서를 참조하세요.

dbt 프로젝트 객체 생성 및 실행

배포할 준비가 되면 dbt 프로젝트 객체를 만들고 기본 환경을 선택해요. Snowflake는 dbt 프로젝트 루트에 env.yml 파일이 있기를 기대해요.

CREATE DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  FROM '@tasty_bytes_dbt_db.integrations.tasty_bytes_dbt_git_stage/branches/main'
  DEFAULT_TARGET = 'prod'
  DEFAULT_ENVIRONMENT = 'prod' -- 선택 사항.
  EXTERNAL_ACCESS_INTEGRATIONS = 'dbt_ext_access';

나중에 ALTER로 기본 환경을 변경하거나 지워요.

ALTER DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  SET DEFAULT_ENVIRONMENT = 'staging';

ALTER DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  UNSET DEFAULT_ENVIRONMENT;
환경 없이 실행 (NO_ENV)

NO_ENV는 예약된 환경 이름이에요(항상 대문자). env.yml에서 어떤 환경도 선택하지 않고 실행하면서도 ENV_VARS로 재정의를 전달하고 싶을 때 사용해요. 생성 시 기본값으로 설정하거나 실행 시 전달할 수 있어요.

CREATE OR REPLACE DBT PROJECT tasty_bytes_dbt_db.dev.dbt_no_env_valid
  FROM '@tasty_bytes_dbt_db.integrations.tasty_bytes_dbt_git_stage/branches/main'
  DEFAULT_ENVIRONMENT = 'NO_ENV';

EXECUTE DBT PROJECT tasty_bytes_dbt_db.dev.dbt_no_env_valid
  ARGS = 'run --target prod'
  ENVIRONMENT = 'NO_ENV'
  ENV_VARS = ('DBT_KEY' = 'some_value');
환경 재정의로 실행

ENVIRONMENT 인자로 단일 실행의 기본 환경을 재정의해요. 환경 이름 하나를 받아들여요.

EXECUTE DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  ARGS = 'run --target prod'
  DBT_VERSION = '1.11.11'
  ENVIRONMENT = 'prod'; -- 선택 사항.
특정 변수 재정의로 실행

ENV_VARS 재정의에서 Snowflake 시크릿을 직접 참조할 수 없어요(예: 'DBT_KEY1'='db.schema.my_secret'). 이는 의도된 설계예요. EXECUTE DBT PROJECT 문은 Query History에 기록되므로, 거기서 원시 시크릿 참조를 허용하면 그 기록을 조회할 수 있는 사람에게 자격 증명이 노출될 수 있어요. 시크릿은 env.yml 파일을 통해 관리하세요.

ENV_VARS로 단일 실행에 대해 개별 환경 변수를 재정의할 수 있어요. 이 재정의는 선택된 환경에 병합되고 최종 우선순위를 가져요. 값은 VARCHAR로 해결되는 SQL, 문자열 리터럴, 세션 변수($var), 또는 바인드 자리표시자(?)일 수 있어요. NULL 값과 VARCHAR/CHAR가 아닌 타입은 거부돼요. SQL 쿼리는 작은따옴표로 전달해야 하고 모든 내부 작은따옴표는 이스케이프해야 해요.

EXECUTE DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  ARGS = 'run --target prod'
  DBT_VERSION = '1.11.11'
  ENV_VARS = (
    'DBT_KEY1' = 'VALUE1',
    'DBT_KEY2' = '{{ select (DATE_TRUNC(\'DAY\', CURRENT_TIMESTAMP()) - INTERVAL \'1 SECOND\')::string }}'
  ); -- 선택 사항. 재정의 키는 DBT_-접두사이고 대문자여야 합니다.

ENV_VARS 값으로 세션 변수를 전달할 수도 있어요. 먼저 세션 변수를 설정한 다음 $로 참조해요.

SET my_user_var = (SELECT CURRENT_USER());

EXECUTE DBT PROJECT tasty_bytes_dbt_db.dev.tasty_bytes_dbt_project
  ARGS = 'run --target prod'
  ENV_VARS = ('DBT_CURRENT_USER' = $my_user_var);

env.yml에서 저장 프로시저 호출

env: 값에서 SELECT * FROM TABLE(...) 구문으로 저장 프로시저를 호출할 수 있어요. CALL 구문은 지원되지 않아요. 이는 로직이 인라인 SQL로는 너무 복잡하거나 여러 환경에서 같은 변환을 재사용하고 싶을 때 유용해요.

env.yml 값은 단일 행과 한 VARCHAR 컬럼으로 해결되어야 하므로, 저장 프로시저는 한 행과 한 컬럼의 테이블을 반환해야 해요. "{{ select * FROM TABLE(procedure_name(...)) }}" 패턴을 사용하세요.

SELECT로 저장 프로시저를 호출하는 방법에 대한 자세한 내용은 SELECT로 저장 프로시저 호출 문서를 참조하세요.

예시: 저장 프로시저로 개발자별 스키마

이 저장 프로시저는 이메일 또는 사용자 이름 문자열을 받아 @ 이후의 모든 것을 제거하고 .를 _로 바꿔요. 결과는 각 개발자에게 깔끔한 스키마 이름이 돼요.

CREATE OR REPLACE PROCEDURE tasty_bytes_dbt_db.public.clean_username (email_input VARCHAR)
RETURNS TABLE (cleaned_username VARCHAR)
LANGUAGE SQL
AS
DECLARE
  res RESULTSET;
BEGIN
  res := (SELECT REPLACE(SPLIT_PART(:email_input, '@', 1), '.', '_') AS cleaned_username);
  RETURN TABLE(res);
END;

-- 예시 1: '[email protected]' 전달
SELECT * FROM TABLE(tasty_bytes_dbt_db.public.clean_username('[email protected]'));
-- 결과: john_doe

-- 예시 2: '[email protected]' 전달
SELECT * FROM TABLE(tasty_bytes_dbt_db.public.clean_username('[email protected]'));
-- 결과: johndoe
env.yml에서 저장 프로시저 호출

env.yml에서 CURRENT_USER()를 입력으로 저장 프로시저를 호출해 각 엔지니어가 자동으로 자신의 스키마를 갖게 해요:

env:
  # 저장 프로시저를 사용한 개발자별 스키마 예시
  DBT_SCHEMA: "{{ select * FROM TABLE(tasty_bytes_dbt_db.public.clean_username(CURRENT_USER())) }}"
  # 원시 SQL의 대안 (저장 프로시저 불필요):
  # DBT_SCHEMA: "{{ select REPLACE(SPLIT_PART(CURRENT_USER(), '@', 1), '.', '_') }}"

두 접근 방식 모두 같은 결과를 만들어요. 저장 프로시저는 로직이 더 복잡하거나 팀 간에 공유될 때 유용해요.

Snowflake CLI 사용하기

Snowflake CLI는 환경 변수를 CI/CD와 외부 오케스트레이터에 연결하는 방법이에요. 이 섹션의 플래그는 Snowflake CLI 3.21 이상이 필요해요.

배포 플래그

  • --env-file-dir: CLI가 저장소의 다른 곳에 있는 env.yml 파일을 포함한 디렉터리를 가리키게 해요. --profiles-dir와 비슷해요. 파일은 배포된 객체로 가져와져, 프로젝트 루트(dbt_project.yml 옆)의 객체 env.yml이 이미 존재하면 덮어써요.
  • --default-env: dbt 프로젝트 객체의 컴파일과 이후 실행을 위한 기본 환경을 설정해요.
  • --external-access-integration: dbt 프로젝트 객체에 외부 접근 통합을 연결해요. 프로젝트가 dbt deps를 통해 비공개 Git 패키지나 원격 의존성을 가져올 때 필요해요. 통합마다 한 번씩 플래그를 전달해요. 관리자 설정 문서를 참조하세요.
snow dbt deploy tester_tasty_bytes_dbt_project --source ./tasty_bytes
snow dbt deploy tester_tasty_bytes_dbt_project --source ./tasty_bytes --env-file-dir ./some/path
snow dbt deploy tester_tasty_bytes_dbt_project --default-env prod

# 기본 target과 외부 접근 통합과 함께 배포 (비공개 Git 패키지에 필요).
snow dbt deploy tasty_bytes_dbt_project --source ./tasty_bytes \
  --default-target prod \
  --external-access-integration dbt_ext_access

실행 플래그

  • --env: 실행 시 env.yml 파일 내의 환경을 선택해요.
  • --env-vars: 파일을 수정하지 않고 실행 시 인라인 키/값 재정의를 적용해요. 여기서 Snowflake 관리 시크릿을 통한 시크릿 주입은 지원되지 않아요.
  • --use-shell-env-vars: 셸 환경 변수를 실행으로 가져와 env.yml을 재정의해요. DBT_ 접두사가 붙은 모든 셸 변수(DBT_ENV_SECRET_* 변수 제외)가 사용되며, env.yml에 정의되지 않은 것도 포함돼요. 이 플래그를 설정하면 --env-vars만 셸 값을 재정의할 수 있어요.
snow dbt execute --dbt-version '1.11.11' --env staging \
  --env-vars '{"DBT_DATABASE": "tasty_bytes_staging_db", "DBT_OTHER_KEY": "value"}' \
  tester_tasty_bytes_dbt_project run

배포 시 --env-file-dir이 배포된 dbt 프로젝트 객체 루트에 저장되는 env.yml을 결정해요. 없으면 CLI는 프로젝트 루트의 env.yml을 배포해요. 실행 시 그 실행은 그 루트 env.yml을 읽어요. --use-shell-env-vars를 추가하면 키가 충돌하는 곳마다 그 값을 DBT_ 접두사 셸 변수로 재정의하고, --env-vars는 셸 값을 포함해 모든 것을 재정의해요.

CI 테스트용 격리된 PR별 데이터베이스 생성 (GitHub Actions)

이 예시는 풀 리퀘스트 번호를 사용해 격리된 스테이징 데이터베이스를 만들고, 그 위에서 변환을 실행하고, 정리해요. GitHub Actions는 셸이 실행되기 전에 ${{github.event.number}}를 풀 리퀘스트 번호로 바꿔요.

이 예시는 제로 복사 클론을 선택해 CI가 기존 데이터의 현실적이고 독립적으로 쓰기 가능한 스냅샷을 얻도록 해요. 클론은 처음에 모든 테이블 데이터를 물리적으로 복사하는 대신 소스 데이터베이스의 기존 마이크로 파티션을 공유해요. 소스와 클론은 데이터가 변함에 따라 추가 스토리지를 소비해요.

테스트가 기존 프로덕션 데이터와 객체의 현실적이고 쓰기 가능한 복사본이 필요하지 않다면 모든 CI 워크플로에서 프로덕션을 클론할 필요는 없어요. defer to production을 사용하면 CI가 프로덕션에서 변경되지 않은 업스트림 테이블을 읽고, 변경된 모델을 별도 테스트 데이터베이스나 스키마에 쓸 수 있어요. 프로덕션 state를 가져와 PR별 클론에 대해 Slim CI를 실행하고 리소스를 정리하는 완전한 워크플로는 dbt Projects on Snowflake용 Slim CI와 PR별 데이터베이스로 CI/CD 설정 튜토리얼을 참조하세요.

# 테스터 객체 배포.
snow dbt deploy tester_tasty_bytes_dbt_project --source ./tasty_bytes

# PR 번호를 사용해 스테이징 데이터베이스 생성.
snow sql -q "CREATE DATABASE tasty_bytes_staging_pr_${{ github.event.number }}_db CLONE tasty_bytes_dbt_db"

# 재정의로 스테이징 데이터베이스에 대해 실행.
snow dbt execute --env staging \
  --env-vars '{"DBT_DATABASE": "tasty_bytes_staging_pr_${{ github.event.number }}_db"}' \
  tester_tasty_bytes_dbt_project run

# PR이 머지된 후 프로덕션 객체에 배포하고 컴파일·이후 실행용 환경 선택.
snow dbt deploy tasty_bytes_dbt_project --default-env prod

# 풀 리퀘스트가 닫힐 때 트리거되는 워크플로에서 같은 PR 번호로 정리.
snow sql -q "DROP DATABASE IF EXISTS tasty_bytes_staging_pr_${{ github.event.pull_request.number }}_db"

관측성(Observability)

여러 Snowflake 화면에서 환경 정보를 볼 수 있어요:

  • SHOW DBT PROJECTS와 GET_DDL은 dbt 프로젝트 객체의 DEFAULT_ENVIRONMENT 속성을 반영해요. 이는 사용자가 CREATE 또는 ALTER 명령으로 객체에 설정해야 하는 속성이에요. env.yml 파일의 default_environment: 섹션에서 파생되는 것이 아니에요.
  • ACCOUNT_USAGE의 DBT_PROJECT_EXECUTION_HISTORY 뷰의 ENVIRONMENT 컬럼은 각 실행에 사용된 환경을 보여줘요.
  • Query History는 특정 EXECUTE DBT PROJECT 문의 ENVIRONMENT 재정의를 보여줘요.
  • Query History는 또한 특정 EXECUTE DBT PROJECT 문의 ENV_VARS 재정의(재정의된 환경 변수)를 보여줘요.

참조(Reference)

지원되는 컨텍스트 함수와 Jinja 헬퍼

env:와 secrets: 섹션은 다른 구문을 지원해요. 아래 표는 각각에서 무엇이 작동하는지 나열해요.

Jinja 컨텍스트 변수 헬퍼 (secrets: 이름 보간)

secrets: 안에서 시크릿 이름을 보간하는 데 사용해요(예: "snowflake_secret: {{current_user}}_git_secret"). 이 섹션에서는 select 문, 괄호, 연결(concatenation)이 허용되지 않아요.

헬퍼 지원
{{ current_user }} 예
{{ current_account }} 예
{{ current_account_name }} 예
{{ current_organization_name }} 예
SQL 컨텍스트 함수 (env: 값, {{ select ... }} 구문)

{{ select ... }}로 감싼 env: 값 안에서 사용해요. 단일 행과 한 VARCHAR 컬럼을 반환하는 어떤 SQL 쿼리든 지원돼요(SELECT column FROM your_control_table 같은 전체 테이블 쿼리 포함).

함수 지원 비고
CURRENT_USER() 예
CURRENT_ROLE() 예
CURRENT_DATABASE() 예
CURRENT_SCHEMA() 예
CURRENT_WAREHOUSE() 예
CURRENT_DATE() / CURRENT_TIME() / CURRENT_TIMESTAMP() 및 별칭 (GETDATE, SYSDATE, SYSTIMESTAMP, LOCALTIME, LOCALTIMESTAMP) 예
날짜·시간 연산 (DATEADD, DATEDIFF, DATE_TRUNC, ADD_MONTHS 등) 예
CURRENT_SESSION() 아니요 dbt는 여러 스레드에 걸쳐 실행되므로 일관된 세션 ID가 없어요.
CURRENT_CLIENT() 아니요
CURRENT_IP_ADDRESS() 아니요
CURRENT_VERSION() 아니요
ALL_USER_NAMES() 아니요
LAST_QUERY_ID() 아니요
INVOKER_ROLE() 아니요
IS_ROLE_IN_SESSION() 아니요
SYS_CONTEXT() 아니요

값이 프로필 파일의 role 또는 warehouse 필드를 공급할 때, Workspaces는 그들을 감싸는 표현식이 아니라 맨 "{{ select CURRENT_ROLE() }}" 또는 "{{ select CURRENT_WAREHOUSE() }}"만 받아들여요. database와 schema 필드는 저장 프로시저를 포함한 SQL 동작의 전체 집합을 받아들여요. 이 제한은 Workspaces에만 적용돼요. 배포된 dbt 프로젝트 객체는 네 필드 모두에 복잡한 SQL을 받아들여요.

dbt 매크로

매크로(예: {{ ref(...) }}, {{ source(...) }})는 env.yml 어디에서도 허용되지 않아요. 표준 dbt Core 환경 변수는 매크로가 사용할 수 있기 전에 해결되어야 하므로, env.yml 안에서 매크로를 허용하면 순환 의존성이 생겨요.

env: 값의 제한된 Jinja

env: 값에서는 {{ select(query) }} Jinja 패턴만 지원돼요. 루프, 조건문, 필터 같은 다른 Jinja 구조물은 허용되지 않아요. 예를 들어 이것은 유효하지 않아요:

env:
  # 지원되지 않음: Jinja 루프는 env: 값에서 허용되지 않습니다.
  DBT_SCHEMA: "{% for schema in schemas %}{{ schema }}{% endfor %}"

대신 {{ select ... }} 안에서 SQL을 사용하세요. 예: "{{ select CURRENT_USER() }}", "{{ select COALESCE(...) }}", 또는 "{{ select * from TABLE(my_stored_proc()) }}".

환경 선택 (높음 → 낮음)

실행마다 하나의 환경이 활성화되며, 이 순서로 선택돼요:

우선순위 출처
1 (가장 높음) EXECUTE DBT PROJECT의 ENVIRONMENT = '...'
2 dbt 프로젝트 객체의 DEFAULT_ENVIRONMENT
3 env.yml의 default_environment

예약된 이름 NO_ENV를 사용해 어떤 환경으로도 실행하지 않을 수 있어요.

값 우선순위 (높음 → 낮음)

우선순위 출처 비고
1 (가장 높음) --env-vars / EXECUTE ... ENV_VARS 최종 우선순위. 선택된 환경에 병합됨.
2 셸 환경 변수 --use-shell-env-vars 사용 시에만. DBT_ 접두사가 붙은 모든 셸 변수(DBT_ENV_SECRET_* 변수 제외)가 사용되며, env.yml에 정의되지 않은 것도 포함됨.
3 (가장 낮음) env.yml의 선택된 환경 커밋된 기본 구성.

이름 지정과 대소문자 규칙

규칙 적용 대상
DBT_ 접두사 (DBT_ENV_CUSTOM_ENV_, DBT_ENV_SECRET_ 포함) env:와 secrets:의 모든 키, 모든 재정의
UPPERCASE 키 env:와 secrets:의 모든 키, 모든 재정의
DBT_ENV_SECRET_ 접두사 secrets:의 모든 키 (값은 ****로 마스킹됨)
SQL이 아닌 일반 텍스트여야 함 모든 환경 이름, env:와 secrets:의 키, 시크릿 값
영어 문자, 숫자, 밑줄, 최대 256자 env:와 secrets:의 모든 키와 환경 이름
대소문자 구분 환경 이름

env: 대비 secrets: 기능

기능 env: secrets:
일반 텍스트 값 예 n/a (시크릿 객체 참조)
SQL 쿼리 "{{ select ... }}" (한 행, 한 VARCHAR 컬럼, 테이블 쿼리 포함) 예 아니요
제한된 컨텍스트 변수 (예: {{ current_user }}, select나 () 없음) 아니요 예
연결 연산자 (예: "{{ select CURRENT_USER() || '_schema' }}") 예 아니요
매크로 아니요 아니요
시크릿 타입 n/a GENERIC_STRING만
사용 전 외부 접근 통합에 대해 검증됨 n/a 예

왜 DBT_ 접두사가 필요한가

DBT_ 접두사는 의도적인 설계 결정이에요.

  • 네임스페이스 격리: CI/CD 파이프라인, Airflow DAG, 컨테이너 호스트는 이미 시스템 수준 변수와 잠재적으로 민감한 자격 증명을 보유해요. DBT_ 접두사는 우연히 같은 이름을 공유하는 인프라 변수와의 이름 충돌을 방지해요.
  • 내장 보안: dbt Core는 이 접두사를 사용해 변수를 다른 보안 파이프라인으로 라우팅해요. DBT_ENV_SECRET_ 변수는 모든 로그에서 자동으로 ****로 마스킹되고 메타데이터 아티팩트에서 제외돼요. 접두사가 없으면 컴파일러가 런타임에 민감한 값과 민감하지 않은 값을 구분할 방법이 없어요.
  • 범위가 제한된 검색: --use-shell-env-vars를 사용하면 Snowflake CLI는 DBT_ 접두사가 붙은 셸 변수만 읽어요(DBT_ENV_SECRET_* 변수 제외). 이렇게 하면 Airflow DAG, 로컬 머신, 또는 컨테이너 호스트에서 읽는 값을 dbt용으로 의도된 변수로만 제한해요.

파일 위치와 크기

  • env.yml을 dbt 프로젝트 루트, dbt_project.yml과 같은 폴더에 두세요.
  • 2MB 제한 (대략 12,000줄).

더 알아보기 (Learn more)