호출자의 권한을 사용하는 Snowpark Container Services 서비스 만들기

소개

이 자습서에서는 사용자를 대신하여 SQL 쿼리를 실행할 때 호출자의 권한 함수를 사용하는 웹 UI 를 제시하는 서비스를 구축하는 방법을 살펴봅니다.

요청에 제공된 쿼리를 실행하는 서비스(query_service)를 만듭니다. 기본적으로 애플리케이션 컨테이너는 서비스의 소유자 역할을 사용하여 서비스 사용자로 Snowflake에 연결됩니다. 그러나 이 애플리케이션은 호출자의 권한 함수를 사용하여 최종 사용자로 서비스 엔드포인트에 연결하고 해당 사용자에게 부여된 권한을 사용합니다.

테스트 시에는 네트워크 수신을 사용하여 서비스에 액세스할 때만 호출자 권한 기능이 지원되므로 웹 브라우저에서 서비스를 사용합니다. 서비스 함수를 사용하여 서비스에 액세스할 때는 호출자의 권한 기능을 사용할 수 없습니다.

이 서비스는 다음을 수행합니다.

  • 하나의 공용 엔드포인트를 노출합니다.

  • 사용자가 서비스 엔드포인트에 로그인하면 웹 서비스(UI)를 통해 쿼리를 제공합니다. 서비스는 Snowflake에서 쿼리를 실행하고 결과를 반환합니다. 이 자습서에서는 다음 SQL 명령을 실행합니다.

    SELECT CURRENT_USER(), CURRENT_ROLE();
    
    Copy

    이 명령은 현재 로그인한 사용자의 이름과 현재 활동 중인 역할을 반환하며, 이 둘은 모두 호출자의 권한 사용 여부에 따라 달라집니다.

    • 호출자 권한이 사용되면 서비스는 호출자 사용자 및 사용자의 기본 역할로 Snowflake에 연결됩니다. 이 명령은 사용자 이름과 기본 역할을 반환합니다.

    • 호출자의 권한이 사용되지 않으면 기본 동작이 시작되어 서비스가 서비스 사용자 및 서비스 소유자 역할로 Snowflake에 연결됩니다. 따라서 이 명령은 서비스 사용자 이름을 SF$SERVICE$unique-id, TEST_ROLE 형식으로 반환합니다.

이 자습서는 다음 두 부분으로 구성됩니다.

파트 1: 서비스를 만들고 테스트합니다. 이 자습서에 제공된 코드를 다운로드하고 단계별 지침을 따릅니다.

  1. 이 자습서를 위한 서비스 코드를 다운로드합니다.

  2. Snowpark Container Services용 Docker 이미지를 만들어 자기 계정의 리포지토리에 업로드합니다.

  3. 서비스를 만듭니다.

  4. 네트워크 수신을 사용하여 서비스와 통신하여 서비스가 노출하는 공용 엔드포인트와 연결합니다. 웹 브라우저를 사용하여 공용 엔드포인트에 로그인하고 SELECT CURRENT_USER(); 명령을 실행합니다. 명령 출력을 확인하여 컨테이너가 로그인한 사용자로 명령을 실행했는지 확인합니다.

파트 2: 서비스를 이해합니다. 이 섹션에서는 서비스 코드에 대한 개요를 제공하고 애플리케이션 코드가 호출자의 권한을 사용하는 방법을 강조합니다.

준비

공통 설정 을 참조하여 이 설명서에 제공된 모든 Snowpark Container Services 자습서에 필요한 필수 구성 요소를 구성하고 Snowflake 리소스를 생성하십시오.

서비스 코드 다운로드하기

쿼리 서비스를 생성하기 위한 코드(Python 애플리케이션)가 제공됩니다.

  1. SnowparkContainerServices-Tutorials.zip 을 다운로드합니다.

  2. 이 파일의 압축을 풀면 각 자습서마다 하나의 디렉터리가 포함된 것을 알 수 있습니다. Tutorial-6-callers-rights 디렉터리에는 다음 파일이 있습니다.

    • Dockerfile

    • main.py

    • templates/basic_ui.html

이미지 만들기 및 업로드

Snowpark Container Services가 지원하는 linux/amd64 플랫폼의 이미지를 만든 다음, 해당 이미지를 계정의 이미지 리포지토리에 업로드합니다(공통 설정 참조).

리포지토리(리포지토리 URL 및 레지스트리 호스트 이름)에 대한 정보가 있어야 이미지를 만들어 업로드할 수 있습니다. 자세한 내용은 레지스트리 및 리포지토리 를 참조하십시오.

리포지토리에 대한 정보 얻기

  1. 리포지토리 URL을 가져오려면 SHOW IMAGE REPOSITORIES SQL 명령을 실행하십시오.

    SHOW IMAGE REPOSITORIES;
    
    Copy
    • 출력의 repository_url 열은 URL을 제공합니다. 아래에 예가 나와 있습니다.

      <orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
      
    • 리포지토리 URL의 호스트 이름은 레지스트리 호스트 이름입니다. 아래에 예가 나와 있습니다.

      <orgname>-<acctname>.registry.snowflakecomputing.com
      

이미지를 만들어 리포지토리에 업로드하기

  1. 터미널 창을 열고 압축을 푼 파일이 포함된 디렉터리로 변경합니다.

  2. Docker 이미지를 만들려면 Docker CLI를 사용하여 다음 docker build 명령을 실행하십시오. 이 명령은 현재 작업 디렉터리(.)를 이미지 만들기에 사용할 파일의 PATH 로 지정합니다.

    docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
    
    Copy
    • image_name 의 경우 query_service:latest 를 사용합니다.

    docker build --rm --platform linux/amd64 -t myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest .
    
    Copy
  3. Snowflake 계정의 리포지토리에 이미지를 업로드합니다. Docker가 사용자를 대신하여 리포지토리에 이미지를 업로드하려면 먼저 Snowflake로 Docker를 인증해야 합니다.

    1. Snowflake 레지스트리로 Docker를 인증하려면 다음 명령을 실행하십시오.

      docker login <registry_hostname> -u <username>
      
      Copy
      • username 의 경우 Snowflake 사용자 이름을 지정합니다. Docker가 비밀번호를 묻는 메시지를 표시합니다.

    2. 이미지를 업로드하려면 다음 명령을 실행하십시오.

      docker push <repository_url>/<image_name>
      
      Copy

      docker push myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest
      
      Copy

서비스를 만듭니다.

이 섹션에서는 서비스(query_service)를 만듭니다.

  1. 컴퓨팅 풀이 준비되었는지, 서비스를 생성할 수 있는 올바른 컨텍스트에 있는지 확인하십시오.

    1. 이전에는 공통 설정 단계에서 컨텍스트를 설정했습니다. 이 단계에서 SQL 문의 올바른 컨텍스트에 있는지 확인하려면 다음을 실행합니다.

    USE ROLE test_role;
    USE DATABASE tutorial_db;
    USE SCHEMA data_schema;
    USE WAREHOUSE tutorial_warehouse;
    
    Copy
    1. 공통 설정 에서 생성한 컴퓨팅 풀이 준비되었는지 확인하려면 DESCRIBE COMPUTE POOL 을 실행하고 stateACTIVE 또는 IDLE 인지 확인하십시오. stateSTARTING 이면 stateACTIVE 또는 IDLE 로 변경될 때까지 기다려야 합니다.

    DESCRIBE COMPUTE POOL tutorial_compute_pool;
    
    Copy
  2. 서비스를 생성하려면 test_role 을 사용하여 다음 명령을 실행합니다.

    CREATE SERVICE query_service
      IN COMPUTE POOL tutorial_compute_pool
      FROM SPECIFICATION $$
        spec:
          containers:
          - name: main
            image: /tutorial_db/data_schema/tutorial_repository/query_service:latest
            env:
              SERVER_PORT: 8000
            readinessProbe:
              port: 8000
              path: /healthcheck
          endpoints:
          - name: execute
            port: 8000
            public: true
        capabilities:
          securityContext:
            executeAsCaller: true
        serviceRoles:
        - name: ui_usage
          endpoints:
          - execute
    $$;
    
    Copy

    참고

    해당 이름의 서비스가 이미 존재하는 경우 DROP SERVICE 명령을 사용하여 이전에 생성된 서비스를 삭제한 다음 이 서비스를 생성합니다.

  3. 방금 만든 서비스에 대한 자세한 정보를 얻으려면 다음 SQL 명령을 실행하십시오. 자세한 내용은 Snowpark Container Services: 서비스 사용하기 섹션을 참조하십시오.

    • 계정의 서비스를 나열하려면 SHOW SERVICES 명령을 실행하십시오.

      SHOW SERVICES;
      
      Copy
    • 서비스 상태를 확인하려면 SHOW SERVICE CONTAINERS IN SERVICE 명령을 실행하십시오.

      SHOW SERVICE CONTAINERS IN SERVICE query_service;
      
      Copy
    • 서비스에 대한 정보를 얻으려면 DESCRIBE SERVICE 명령을 실행하십시오.

      DESCRIBE SERVICE query_service;
      
      Copy

서비스 사용

이 섹션에서는 서비스에 대해 구성된 호출자의 권한 이 작동하는지 확인합니다. 브라우저에서 공용 엔드포인트에 로그인하여 쿼리를 실행한 후 서비스에서 만든 Snowflake 세션이 서비스 사용자가 아닌 호출 사용자로 작동하는지 확인합니다.

먼저, 이 섹션의 SQL 문에 대한 컨텍스트를 설정하려면 다음을 실행합니다.

USE ROLE test_role;
USE DATABASE tutorial_db;
USE SCHEMA data_schema;
USE WAREHOUSE tutorial_warehouse;
Copy

이 서비스는 공용 엔드포인트를 노출하므로(CREATE SERVICE 명령에 제공된 인라인 사양 참조), 먼저 웹 브라우저를 사용하여 엔드포인트에 로그인한 다음 서비스가 인터넷에 노출하는 웹 UI 를 사용하여 서비스 엔드포인트로 쿼리 요청을 전송합니다.

  1. 서비스가 노출하는 공용 엔드포인트의 URL 을 찾습니다.

    SHOW ENDPOINTS IN SERVICE query_service;
    
    Copy

    응답의 ingress_url 열은 URL을 제공합니다.

    p6bye-myorg-myacct.snowflakecomputing.app
    
  2. 엔드포인트 URL에 /ui 를 추가하고 웹 브라우저에 붙여넣습니다. 그러면 서비스가 ui() 함수를 실행하게 됩니다(main.py 참조).

    엔드포인트 URL에 처음 액세스하면 Snowflake에 로그인하라는 메시지가 표시됩니다.

  3. 서비스를 만들 때 사용한 것과 동일한 사용자를 사용합니다. 로그인에 성공하면 웹 서비스에 다음 Web UI 가 표시됩니다.

    쿼리 서비스와 통신하기 위한 웹 양식.

    텍스트 상자에 다음 명령을 입력하고 Enter 키를 누르면 결과를 확인할 수 있습니다.

    SELECT CURRENT_USER(), CURRENT_ROLE()DONE;
    
    Copy

    서비스 사양에 executeAsCaller 함수를 포함했기 때문에 요청이 도착하면 Snowflake는 요청에 Sf-Context-Current-User-Token 헤더를 삽입한 다음 요청을 서비스 엔드포인트로 전달합니다.

    설명을 위해 이 자습서의 서비스 코드는 호출자와 서비스 사용자 모두로 쿼리를 실행합니다.

    쿼리 서비스와 통신하기 위한 웹 양식.
    • 호출자(수신 사용자)를 대신하여 쿼리를 실행합니다. 이 경우 코드는 Snowflake가 제공하는 사용자 토큰을 사용하여 Snowflake와 연결하기 위한 로그인 토큰을 구성합니다. 따라서 이 서비스는 호출자의 권한을 사용합니다. Snowflake는 호출자를 대신하여 쿼리를 실행하여 쿼리 결과에 호출자의 이름과 활동 중인 역할 이름을 표시합니다. 예:

      ['TESTUSER, PUBLIC']
      
    • 서비스 사용자를 대신하여 쿼리를 실행합니다. 이 경우 코드는 Snowflake와 연결하기 위해 로그인 토큰을 구성할 때 요청에 제공한 사용자 토큰을 사용하지 않습니다. 따라서 이 서비스는 호출자의 권한을 활용하지 않고, 서비스 사용자를 대신하여 Snowflake가 쿼리를 실행합니다. 쿼리 결과에는 서비스 사용자의 이름(서비스 이름과 동일)과 활동 중인 역할이 표시됩니다.

      ['QUERY_SERVICE, TEST_ROLE']
      

서비스가 호출자를 대신하여 쿼리(SELECT CURRENT_USER(), CURRENT_ROLE();)를 실행할 때, 이 간단한 쿼리를 실행하기 위해 사용자의 웨어하우스가 필요하지 않습니다. 따라서 이 서비스에는 호출자 권한 부여 가 필요하지 않았습니다. 다음 섹션에서는 서비스가 호출하는 사용자를 대신하여 서비스에 호출자 권한 을 부여해야 하는 사소하지 않은 쿼리를 실행합니다.

참고

프로그래밍 방식으로 수신 엔드포인트에 액세스할 수 있습니다. 샘플 코드는 Snowflake 외부의 공용 엔드포인트 액세스 및 인증 섹션을 참조하십시오. 코드의 엔드포인트 URL에 /ui 를 추가해야 Snowflake가 서비스 코드의 ui() 함수로 요청을 라우팅할 수 있습니다.

호출자 권한 부여와 함께 서비스 사용하기

이 섹션에서 서비스는 호출자(서비스의 수신 엔드포인트에 로그인하는 사용자)를 대신하여 다음 쿼리를 실행합니다.

SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
Copy

이 서비스에는 테이블에 액세스할 수 있는 권한이 없으며 기본 웨어하우스에서 쿼리를 실행할 수 있는 권한이 없습니다. 서비스가 호출자를 대신하여 이 쿼리를 실행할 수 있도록 하려면 필요한 호출자 권한 을 서비스에 부여합니다.

시나리오를 보여주기 위해 새 역할(ingress_user_role)과 새 역할은 액세스할 수 있지만 서비스의 소유자 역할(test_role)은 액세스할 수 없는 테이블(ingress_user_table)을 만듭니다. 따라서 서비스에서 서비스 자격 증명을 사용하여 쿼리를 실행하려고 하면 Snowflake는 오류를 반환합니다. 그러나 서비스가 사용자를 대신하여 쿼리를 실행하면 Snowflake가 쿼리를 실행하고 결과를 반환합니다.

역할 및 리소스 만들기

  1. 이 역할만 액세스할 수 있는 역할(ingress_user_role)과 데이터베이스(ingress_user_db)를 만듭니다. 그런 다음 사용자에게 이 역할을 부여하여 사용자가 서비스의 공용 엔드포인트에 로그인하고 이 테이블을 쿼리할 수 있도록 합니다.

    USE ROLE accountadmin;
    
    CREATE ROLE ingress_user_role;
    GRANT ROLE ingress_user_role TO USER <your_user_name>;
    
    GRANT USAGE ON WAREHOUSE tutorial_warehouse TO ROLE ingress_user_role;
    
    CREATE DATABASE IF NOT EXISTS ingress_user_db;
    GRANT OWNERSHIP ON DATABASE ingress_user_db TO ROLE ingress_user_role COPY CURRENT GRANTS;
    
    Copy
  2. ingress_user_role 역할만 액세스할 수 있는 테이블(ingress_user_table)을 만듭니다.

    USE ROLE ingress_user_role;
    
    CREATE SCHEMA IF NOT EXISTS ingress_user_db.ingress_user_schema;
    USE WAREHOUSE tutorial_warehouse;
    CREATE TABLE ingress_user_db.ingress_user_schema.ingress_user_table (col string) AS (
        SELECT 'this table is only accessible to the ingress_user_role'
    );
    
    Copy

    서비스가 호출자를 대신하여 테이블을 쿼리하려고 할 때 서비스는 서비스를 생성하는 데 사용된 역할(서비스의 소유자 역할)인 test_role 로만 작업한다는 점에 유의하십시오. 이 역할에는 사용자 테이블에 액세스할 수 있는 권한이 없습니다.

  3. ingress_user_db 데이터베이스의 테이블을 쿼리할 수 있도록 서비스 소유자 역할(test_role)에 호출자 권한을 부여합니다. 이 권한은 다음 사항에 해당하는 경우에만 서비스에서 이 데이터베이스의 테이블을 쿼리할 수 있도록 허용합니다.

    • 이 서비스는 호출자 권한 세션 을 사용하고 있습니다.

    • 세션에서 호출자는 이러한 쿼리를 실행할 권한도 갖습니다.

    USE ROLE accountadmin;
    
    GRANT CALLER USAGE ON DATABASE ingress_user_db TO ROLE test_role;
    GRANT INHERITED CALLER USAGE ON ALL SCHEMAS IN DATABASE ingress_user_db TO ROLE test_role;
    GRANT INHERITED CALLER SELECT ON ALL TABLES IN DATABASE ingress_user_db TO ROLE test_role;
    GRANT CALLER USAGE ON WAREHOUSE tutorial_warehouse TO ROLE test_role;
    SHOW CALLER GRANTS TO ROLE test_role;
    
    Copy
  4. 기본 웨어하우스 및 기본 보조 역할을 구성합니다.

    사용자에 대한 세션이 만들어지면 Snowflake는 로그인한 사용자의 기본 1차 역할, 기본 2차 역할 및 기본 웨어하우스를 활성화합니다. 이 자습서에서,

    • DEFAULT_SECONDARY_ROLES 를 ALL 로 설정하여 현재 사용자에 대한 세션이 생성될 때 현재 보조 역할이 사용자에게 부여된 모든 역할로 설정되도록 Snowflake를 설정합니다.

    • 또한 ingress_user_table 쿼리가 실행되는 기본 웨어하우스를 tutorial_warehouse 로 설정합니다.

    ALTER USER SET DEFAULT_SECONDARY_ROLES = ('ALL');
    ALTER USER SET DEFAULT_WAREHOUSE = TUTORIAL_WAREHOUSE;
    
    Copy

    다음 사항을 참고하십시오.

    • 이 자습서에서는 서비스의 공용 엔드포인트에 로그인합니다. 사용자는 test_role 을 기본 역할로, ingress_user_role 을 보조 역할로 사용합니다. 이렇게 하면 세션에서 ingress_user_role 에서 허용하는 모든 작업을 수행할 수 있습니다.

    • 기본 역할과 기본 웨어하우스는 서비스가 사용자를 대신하여 세션을 설정할 때 활성화되는 역할과 웨어하우스에만 영향을 줍니다. 호출자의 권한 세션이 설정된 후에는 역할을 변경할 수 없지만 웨어하우스는 변경할 수 있습니다.

서비스 사용 및 호출자 권한 테스트하기

  1. 서비스가 노출하는 공용 엔드포인트의 URL 을 찾습니다.

    SHOW ENDPOINTS IN SERVICE tutorial_db.data_schema.query_service;
    
    Copy

    응답의 ingress_url 열은 URL을 제공합니다.

    p6bye-myorg-myacct.snowflakecomputing.app
    
  2. 엔드포인트 URL에 /ui 를 추가하고 웹 브라우저에 붙여넣습니다. 그러면 서비스가 ui() 함수를 실행합니다(echo_service.py 참조). 엔드포인트 URL 에 처음 접속하면 Snowflake에 로그인하라는 메시지가 표시됩니다. 이 테스트에서는 서비스를 만드는 데 사용한 것과 동일한 사용자를 사용하여 사용자에게 필요한 권한이 있는지 확인하십시오.

  3. 서비스를 만들 때 사용한 것과 동일한 사용자를 사용합니다. 로그인에 성공하면 웹 서비스에 다음 Web UI 가 표시됩니다.

    쿼리 서비스와 통신하기 위한 웹 양식.

    텍스트 상자에 다음 명령을 입력하고 Enter 키를 누르면 결과를 확인할 수 있습니다.

    SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
    
    Copy

    설명을 위해 이 자습서의 서비스 코드는 호출자와 서비스 사용자 모두로 쿼리를 실행합니다.

    • 호출자(수신 사용자)를 대신하여 쿼리를 실행합니다. 이 경우 코드는 Snowflake가 제공하는 사용자 토큰을 사용하여 Snowflake와 연결하기 위한 로그인 토큰을 구성합니다. 따라서 이 서비스는 호출자의 권한을 사용합니다. Snowflake가 호출자를 대신하여 쿼리를 실행합니다. 호출자는 ingress_user_table 테이블을 쿼리할 권한이 있는 ingress_user_role role 을 사용하고 있으므로 쿼리 결과에서 한 행이 반환됩니다.

      ['this table is only accessible to ingress_user_role']
      
    • 서비스 사용자를 대신하여 쿼리를 실행합니다. 이 경우 코드는 Snowflake와 연결하기 위해 로그인 토큰을 구성할 때 요청에 제공한 사용자 토큰을 사용하지 않습니다. 따라서 Snowflake는 서비스 사용자를 대신하여 쿼리를 실행합니다. 서비스 소유자가 테이블을 쿼리할 권한이 없는 기본 test_role 을 사용하기 때문에 오류가 표시됩니다.

      Encountered an error when executing query:... SQL compilation error: Database 'INGRESS_USER_DB' does not exist or not authorized.
      
    Echo 서비스와 통신하기 위한 웹 양식.

정리

생성한 청구 가능한 리소스를 제거해야 합니다. 자세한 내용은 자습서 3 의 5단계를 참조하십시오.

서비스 코드 검토하기

이 섹션에서는 다음 주제를 다룹니다.

자습서 코드 검토

1단계에서 다운로드한 zip 파일에는 다음 파일이 포함됩니다.

  • Dockerfile

  • main.py

  • templates/basic_ui.html

서비스를 만들 때 서비스 사양도 사용합니다. 다음 섹션에서는 이러한 코드 구성 요소가 함께 작동하여 서비스를 만드는 방법을 설명합니다.

main.py 파일

이 Python 파일에는 요청에서 쿼리를 실행하고 쿼리 결과를 반환하는 최소한의 HTTP 서버를 구현하는 코드가 포함되어 있습니다. 이 코드는 에코 요청을 제출하기 위한 웹 사용자 인터페이스(UI)를 제공합니다.

from flask import Flask
from flask import request
from flask import render_template
import logging
import os
import sys

from snowflake.snowpark import Session
from snowflake.snowpark.exceptions import *

# Environment variables below will be automatically populated by Snowflake.
SNOWFLAKE_ACCOUNT = os.getenv("SNOWFLAKE_ACCOUNT")
SNOWFLAKE_HOST = os.getenv("SNOWFLAKE_HOST")
SNOWFLAKE_DATABASE = os.getenv("SNOWFLAKE_DATABASE")
SNOWFLAKE_SCHEMA = os.getenv("SNOWFLAKE_SCHEMA")

# Custom environment variables
SNOWFLAKE_USER = os.getenv("SNOWFLAKE_USER")
SNOWFLAKE_PASSWORD = os.getenv("SNOWFLAKE_PASSWORD")
SNOWFLAKE_ROLE = os.getenv("SNOWFLAKE_ROLE")
SNOWFLAKE_WAREHOUSE = os.getenv("SNOWFLAKE_WAREHOUSE")

SERVICE_HOST = os.getenv("SERVER_HOST", "0.0.0.0")
SERVER_PORT = os.getenv("SERVER_PORT", 8080)


def get_logger(logger_name):
    logger = logging.getLogger(logger_name)
    logger.setLevel(logging.DEBUG)
    handler = logging.StreamHandler(sys.stdout)
    handler.setLevel(logging.DEBUG)
    handler.setFormatter(
        logging.Formatter("%(name)s [%(asctime)s] [%(levelname)s] %(message)s")
    )
    logger.addHandler(handler)
    return logger


def get_login_token():
    """
    Read the login token supplied automatically by Snowflake. These tokens
    are short lived and should always be read right before creating any new connection.
    """
    with open("/snowflake/session/token", "r") as f:
        return f.read()


def get_connection_params(ingress_user_token=None):
    """
    Construct Snowflake connection params from environment variables.
    """
    if os.path.exists("/snowflake/session/token"):
        if ingress_user_token:
            logger.info("Creating a session on behalf of user.")
            token = get_login_token() + "." + ingress_user_token
        else:
            logger.info("Creating a session as service user.")
            token = get_login_token()

        return {
            "account": SNOWFLAKE_ACCOUNT,
            "host": SNOWFLAKE_HOST,
            "authenticator": "oauth",
            "token": token,
            "warehouse": SNOWFLAKE_WAREHOUSE,
            "database": SNOWFLAKE_DATABASE,
            "schema": SNOWFLAKE_SCHEMA,
        }
    else:
        return {
            "account": SNOWFLAKE_ACCOUNT,
            "host": SNOWFLAKE_HOST,
            "user": SNOWFLAKE_USER,
            "password": SNOWFLAKE_PASSWORD,
            "role": SNOWFLAKE_ROLE,
            "warehouse": SNOWFLAKE_WAREHOUSE,
            "database": SNOWFLAKE_DATABASE,
            "schema": SNOWFLAKE_SCHEMA,
        }


logger = get_logger("query-service")
app = Flask(__name__)


@app.get("/healthcheck")
def readiness_probe():
    return "I'm ready!"


@app.route("/ui", methods=["GET", "POST"])
def ui():
    """
    Main handler for providing a web UI.
    """
    if request.method == "POST":
        # get ingress user token
        ingress_user = request.headers.get("Sf-Context-Current-User")
        ingress_user_token = request.headers.get("Sf-Context-Current-User-Token")

        if ingress_user:
            logger.info(f"Received a request from user {ingress_user}")

        # getting input in HTML form
        query = request.form.get("query")
        if query:
            logger.info(f"Received a request for query: {query}.")
            query_result_ingress_user = (
                run_query(query, ingress_user_token)
                if ingress_user_token
                else "Token is missing. Can't execute as ingress user."
            )
            query_result_service_user = run_query(query)
            return render_template(
                "basic_ui.html",
                query_input=query,
                query_result_ingress_user=query_result_ingress_user,
                query_result_service_user=query_result_service_user,
            )
    return render_template("basic_ui.html")


@app.route("/query", methods=["GET"])
def query():
    """
    Main handler for providing programmatic access.
    """
    # get ingress user token
    query = request.args.get("query")
    logger.info(f"Received query request: {query}.")
    if query:
        ingress_user = request.headers.get("Sf-Context-Current-User")
        ingress_user_token = request.headers.get("Sf-Context-Current-User-Token")

        if ingress_user:
            logger.info(f"Received a request from user {ingress_user}")

        res = run_query(query, ingress_user_token)
        return str(res)
    return "DONE"


def run_query(query, ingress_user_token=None):
    # start a Snowflake session as the ingress user
    try:
        with Session.builder.configs(
            get_connection_params(ingress_user_token)
        ).create() as session:
            logger.info(
                f"Snowflake connection established (id={session.session_id}). Now executing query: {query}."
            )
            try:
                res = session.sql(query).collect()
                logger.info(f"Query execution done: {query}.")
                return (
                    "[Empty Result]"
                    if len(res) == 0
                    else [", ".join(row) for row in res]
                )
            except Exception as e:
                return "Encountered an error when executing query: " + str(e)
    except Exception as e:
        return "Encountered an error when connecting to Snowflake: " + str(e)

if __name__ == '__main__':
  app.run(host=SERVICE_HOST, port=SERVER_PORT)
Copy

코드에서는 다음 사항이 적용됩니다.

  • ui 함수는 다음 웹 양식을 표시하고 웹 양식에서 제출된 쿼리 요청을 처리합니다.

    쿼리 서비스와 통신하기 위한 웹 양식.

    이 함수는 @app.route() 데코레이터를 사용하여 /ui 에 대한 요청이 이 함수로 처리되도록 지정합니다.

    @app.route("/ui", methods=["GET", "POST"])
    def ui():
    
    Copy

    쿼리 서비스는 execute 엔드포인트를 공개적으로 노출하여(서비스 생성 시 제공한 인라인 서비스 사양 참조) 웹을 통해 서비스와 통신할 수 있도록 합니다. 브라우저에 /ui가 추가된 공용 엔드포인트의 URL을 로드하면 브라우저가 이 경로에 대한 HTTP GET 요청을 보내고 서버는 요청을 이 함수로 라우팅합니다. 이 함수는 사용자가 쿼리를 입력할 수 있는 간단한 HTML 양식을 실행하고 반환합니다.

    사용자가 쿼리를 입력하고 양식을 제출하면 브라우저는 이 경로에 대한 HTTP POST 요청을 보냅니다. 서비스 사양에 executeAsCaller 기능이 포함되어 있으므로 Snowflake는 들어오는 요청에 Sf-Context-Current-User-Token 헤더를 추가하고 이 함수에 요청을 전달합니다(호출자 권한을 사용하여 Snowflake에 연결하기 참조).

    이 코드는 run_query 함수를 두 번 실행합니다.

    • 수신 사용자로 실행. 이 경우 로그인 토큰은 OAuth 토큰과 수신 사용자 토큰을 모두 연결한 것입니다.

      token = get_login_token() + "." + ingress_user_token
      
      Copy
    • 서비스 사용자로 실행. 이 경우 로그인 토큰은 OAuth 토큰뿐입니다.

      token = get_login_token()
      
      Copy
  • readiness_probe 함수는 @app.get() 데코레이터를 사용하여 /healthcheck 에 대한 요청이 이 함수로 처리되도록 지정합니다.

    @app.get("/healthcheck")
    def readiness_probe():
    
    Copy

    이 함수를 사용하면 Snowflake가 서비스 준비 상태를 확인할 수 있습니다. 컨테이너가 시작되면 Snowflake는 애플리케이션이 작동하고 서비스가 요청을 처리할 준비가 되었는지 확인하려고 합니다. Snowflake는 (상태 프로브, 준비 상태 프로브로서) 이 경로를 사용하여 HTTP GET 요청을 보내 정상 컨테이너만 트래픽을 제공하도록 보장합니다. 이 함수로 원하는 것을 무엇이든 할 수 있습니다.

  • get_logger 함수는 로깅 설정에 도움이 됩니다.

Dockerfile

이 파일에는 Docker를 사용하여 이미지를 만드는 모든 명령이 포함되어 있습니다.

ARG BASE_IMAGE=python:3.10-slim-buster
FROM $BASE_IMAGE
COPY main.py ./
COPY templates/ ./templates/
RUN pip install --upgrade pip && pip install flask snowflake-snowpark-python
CMD ["python", "main.py"]
Copy

Dockerfile에는 Docker 컨테이너에 Flask 라이브러리를 설치하기 위한 지침이 포함되어 있습니다. main.py 의 코드는 Flask 라이브러리를 사용하여 HTTP 요청을 처리합니다.

/template/basic_ui.html

쿼리 서비스는 echoendpoint 엔드포인트를 공개적으로 노출하므로(서비스 사양 참조) 웹을 통해 서비스와 통신할 수 있습니다. 브라우저에 /ui 가 추가된 공용 엔드포인트 URL 을 로드하면 쿼리 서비스가 이 양식을 표시합니다.

쿼리 서비스와 통신하기 위한 웹 양식.

양식에 쿼리를 입력하고 양식을 제출할 수 있으며, 그러면 서비스가 HTTP 응답으로 결과를 반환합니다.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Welcome to the query service!</title>
  </head>
  <body>
    <h1>Welcome to the query service!</h1>
    <form action="{{ url_for("ui") }}" method="post">
      <label for="query">query:<label><br>
      <input type="text" id="query" name="query" size="50"><br>
    </form>
    <h2>Query:</h2>
    {{ query_input }}
    <h2>Result (executed on behalf of ingress user):</h2>
    {{ query_result_ingress_user }}
    <h2>Result (executed as service user):</h2>
    {{ query_result_service_user }}
  </body>
</html>
Copy

서비스 사양

Snowflake는 사용자가 이 사양에 제공하는 정보를 사용하여 서비스를 구성하고 실행합니다.

spec:
  containers:
  - name: main
    image: /tutorial_db/data_schema/tutorial_repository/query_service:latest
    env:
      SERVER_PORT: 8000
    readinessProbe:
      port: 8000
      path: /healthcheck
  endpoints:
  - name: execute
    port: 8000
    public: true
capabilities:
  securityContext:
    executeAsCaller: true
serviceRoles:
- name: ui_usage
  endpoints:
  - execute
Copy

서비스 사양에서 spec, capabilities, serviceRoles 는 최상위 필드입니다.

  • spec 에서 사양 세부 정보를 제공합니다(서비스 사양 참조 참조). 이 서비스는 공용 웹에서 서비스에 대한 수신 액세스를 허용하는 하나의 공용 엔드포인트(execute)를 노출한다는 점에 유의하십시오.

  • capabilities executeAsCaller 기능을 지정합니다. 이는 애플리케이션이 호출자의 권한 을 사용하려고 한다는 것을 Snowflake에게 알려줍니다.

  • serviceRoles 는 USAGE 권한을 부여할 서비스 역할(ui_usage)과 엔드포인트 이름(execute)을 지정합니다.

  • readinessProbe 필드는 Snowflake가 준비 상태 프로브에 HTTP GET 요청을 보내 서비스가 트래픽을 처리할 준비가 되었는지 확인하는 데 사용할 수 있는 portpath 를 식별합니다.

    서비스 코드(echo_python.py)는 다음과 같이 준비 상태 프로브를 구현합니다.

    @app.get("/healthcheck")
    def readiness_probe():
    
    Copy

    따라서 사양 파일에는 이에 따라 container.readinessProbe 필드가 포함됩니다.

서비스 사양에 대한 자세한 내용은 서비스 사양 참조 섹션을 참조하십시오.

다음에는 무엇을 해야 합니까?

이제 이 자습서를 마쳤으므로 서비스 사용하기 로 돌아가서 다른 항목을 살펴볼 수 있습니다.