호출자의 권한을 사용하는 Snowpark Container Services 서비스 만들기¶
소개¶
이 자습서에서는 사용자를 대신하여 SQL 쿼리를 실행할 때 호출자의 권한 함수를 사용하는 웹 UI 를 제시하는 서비스를 구축하는 방법을 살펴봅니다.
요청에 제공된 쿼리를 실행하는 서비스(query_service
)를 만듭니다. 기본적으로 애플리케이션 컨테이너는 서비스의 소유자 역할을 사용하여 서비스 사용자로 Snowflake에 연결됩니다. 그러나 이 애플리케이션은 호출자의 권한 함수를 사용하여 최종 사용자로 서비스 엔드포인트에 연결하고 해당 사용자에게 부여된 권한을 사용합니다.
테스트 시에는 네트워크 수신을 사용하여 서비스에 액세스할 때만 호출자 권한 기능이 지원되므로 웹 브라우저에서 서비스를 사용합니다. 서비스 함수를 사용하여 서비스에 액세스할 때는 호출자의 권한 기능을 사용할 수 없습니다.
이 서비스는 다음을 수행합니다.
하나의 공용 엔드포인트를 노출합니다.
사용자가 서비스 엔드포인트에 로그인하면 웹 서비스(UI)를 통해 쿼리를 제공합니다. 서비스는 Snowflake에서 쿼리를 실행하고 결과를 반환합니다. 이 자습서에서는 다음 SQL 명령을 실행합니다.
SELECT CURRENT_USER(), CURRENT_ROLE();
이 명령은 현재 로그인한 사용자의 이름과 현재 활동 중인 역할을 반환하며, 이 둘은 모두 호출자의 권한 사용 여부에 따라 달라집니다.
호출자 권한이 사용되면 서비스는 호출자 사용자 및 사용자의 기본 역할로 Snowflake에 연결됩니다. 이 명령은 사용자 이름과 기본 역할을 반환합니다.
호출자의 권한이 사용되지 않으면 기본 동작이 시작되어 서비스가 서비스 사용자 및 서비스 소유자 역할로 Snowflake에 연결됩니다. 따라서 이 명령은 서비스 사용자 이름을
SF$SERVICE$unique-id
,TEST_ROLE
형식으로 반환합니다.
이 자습서는 다음 두 부분으로 구성됩니다.
파트 1: 서비스를 만들고 테스트합니다. 이 자습서에 제공된 코드를 다운로드하고 단계별 지침을 따릅니다.
이 자습서를 위한 서비스 코드를 다운로드합니다.
Snowpark Container Services용 Docker 이미지를 만들어 자기 계정의 리포지토리에 업로드합니다.
서비스를 만듭니다.
네트워크 수신을 사용하여 서비스와 통신하여 서비스가 노출하는 공용 엔드포인트와 연결합니다. 웹 브라우저를 사용하여 공용 엔드포인트에 로그인하고 SELECT CURRENT_USER(); 명령을 실행합니다. 명령 출력을 확인하여 컨테이너가 로그인한 사용자로 명령을 실행했는지 확인합니다.
파트 2: 서비스를 이해합니다. 이 섹션에서는 서비스 코드에 대한 개요를 제공하고 애플리케이션 코드가 호출자의 권한을 사용하는 방법을 강조합니다.
준비¶
공통 설정 을 참조하여 이 설명서에 제공된 모든 Snowpark Container Services 자습서에 필요한 필수 구성 요소를 구성하고 Snowflake 리소스를 생성하십시오.
서비스 코드 다운로드하기¶
쿼리 서비스를 생성하기 위한 코드(Python 애플리케이션)가 제공됩니다.
SnowparkContainerServices-Tutorials.zip
을 다운로드합니다.이 파일의 압축을 풀면 각 자습서마다 하나의 디렉터리가 포함된 것을 알 수 있습니다.
Tutorial-6-callers-rights
디렉터리에는 다음 파일이 있습니다.Dockerfile
main.py
templates/basic_ui.html
이미지 만들기 및 업로드¶
Snowpark Container Services가 지원하는 linux/amd64 플랫폼의 이미지를 만든 다음, 해당 이미지를 계정의 이미지 리포지토리에 업로드합니다(공통 설정 참조).
리포지토리(리포지토리 URL 및 레지스트리 호스트 이름)에 대한 정보가 있어야 이미지를 만들어 업로드할 수 있습니다. 자세한 내용은 레지스트리 및 리포지토리 를 참조하십시오.
리포지토리에 대한 정보 얻기
리포지토리 URL을 가져오려면 SHOW IMAGE REPOSITORIES SQL 명령을 실행하십시오.
SHOW IMAGE REPOSITORIES;
출력의
repository_url
열은 URL을 제공합니다. 아래에 예가 나와 있습니다.<orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
리포지토리 URL의 호스트 이름은 레지스트리 호스트 이름입니다. 아래에 예가 나와 있습니다.
<orgname>-<acctname>.registry.snowflakecomputing.com
이미지를 만들어 리포지토리에 업로드하기
터미널 창을 열고 압축을 푼 파일이 포함된 디렉터리로 변경합니다.
Docker 이미지를 만들려면 Docker CLI를 사용하여 다음
docker build
명령을 실행하십시오. 이 명령은 현재 작업 디렉터리(.
)를 이미지 만들기에 사용할 파일의PATH
로 지정합니다.docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
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 .
Snowflake 계정의 리포지토리에 이미지를 업로드합니다. Docker가 사용자를 대신하여 리포지토리에 이미지를 업로드하려면 먼저 Snowflake로 Docker를 인증해야 합니다.
Snowflake 레지스트리로 Docker를 인증하려면 다음 명령을 실행하십시오.
docker login <registry_hostname> -u <username>
username
의 경우 Snowflake 사용자 이름을 지정합니다. Docker가 비밀번호를 묻는 메시지를 표시합니다.
이미지를 업로드하려면 다음 명령을 실행하십시오.
docker push <repository_url>/<image_name>
예
docker push myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/query_service:latest
서비스를 만듭니다.¶
이 섹션에서는 서비스(query_service)를 만듭니다.
컴퓨팅 풀이 준비되었는지, 서비스를 생성할 수 있는 올바른 컨텍스트에 있는지 확인하십시오.
이전에는 공통 설정 단계에서 컨텍스트를 설정했습니다. 이 단계에서 SQL 문의 올바른 컨텍스트에 있는지 확인하려면 다음을 실행합니다.
USE ROLE test_role; USE DATABASE tutorial_db; USE SCHEMA data_schema; USE WAREHOUSE tutorial_warehouse;
공통 설정 에서 생성한 컴퓨팅 풀이 준비되었는지 확인하려면
DESCRIBE COMPUTE POOL
을 실행하고state
가ACTIVE
또는IDLE
인지 확인하십시오.state
가STARTING
이면state
가ACTIVE
또는IDLE
로 변경될 때까지 기다려야 합니다.
DESCRIBE COMPUTE POOL tutorial_compute_pool;
서비스를 생성하려면
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 $$;
참고
해당 이름의 서비스가 이미 존재하는 경우 DROP SERVICE 명령을 사용하여 이전에 생성된 서비스를 삭제한 다음 이 서비스를 생성합니다.
방금 만든 서비스에 대한 자세한 정보를 얻으려면 다음 SQL 명령을 실행하십시오. 자세한 내용은 Snowpark Container Services: 서비스 사용하기 섹션을 참조하십시오.
계정의 서비스를 나열하려면 SHOW SERVICES 명령을 실행하십시오.
SHOW SERVICES;
서비스 상태를 확인하려면 SHOW SERVICE CONTAINERS IN SERVICE 명령을 실행하십시오.
SHOW SERVICE CONTAINERS IN SERVICE query_service;
서비스에 대한 정보를 얻으려면 DESCRIBE SERVICE 명령을 실행하십시오.
DESCRIBE SERVICE query_service;
서비스 사용¶
이 섹션에서는 서비스에 대해 구성된 호출자의 권한 이 작동하는지 확인합니다. 브라우저에서 공용 엔드포인트에 로그인하여 쿼리를 실행한 후 서비스에서 만든 Snowflake 세션이 서비스 사용자가 아닌 호출 사용자로 작동하는지 확인합니다.
먼저, 이 섹션의 SQL 문에 대한 컨텍스트를 설정하려면 다음을 실행합니다.
USE ROLE test_role;
USE DATABASE tutorial_db;
USE SCHEMA data_schema;
USE WAREHOUSE tutorial_warehouse;
이 서비스는 공용 엔드포인트를 노출하므로(CREATE SERVICE 명령에 제공된 인라인 사양 참조), 먼저 웹 브라우저를 사용하여 엔드포인트에 로그인한 다음 서비스가 인터넷에 노출하는 웹 UI 를 사용하여 서비스 엔드포인트로 쿼리 요청을 전송합니다.
서비스가 노출하는 공용 엔드포인트의 URL 을 찾습니다.
SHOW ENDPOINTS IN SERVICE query_service;
응답의
ingress_url
열은 URL을 제공합니다.예
p6bye-myorg-myacct.snowflakecomputing.app
엔드포인트 URL에
/ui
를 추가하고 웹 브라우저에 붙여넣습니다. 그러면 서비스가ui()
함수를 실행하게 됩니다(main.py
참조).엔드포인트 URL에 처음 액세스하면 Snowflake에 로그인하라는 메시지가 표시됩니다.
서비스를 만들 때 사용한 것과 동일한 사용자를 사용합니다. 로그인에 성공하면 웹 서비스에 다음 Web UI 가 표시됩니다.
텍스트 상자에 다음 명령을 입력하고 Enter 키를 누르면 결과를 확인할 수 있습니다.
SELECT CURRENT_USER(), CURRENT_ROLE()DONE;
서비스 사양에
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;
이 서비스에는 테이블에 액세스할 수 있는 권한이 없으며 기본 웨어하우스에서 쿼리를 실행할 수 있는 권한이 없습니다. 서비스가 호출자를 대신하여 이 쿼리를 실행할 수 있도록 하려면 필요한 호출자 권한 을 서비스에 부여합니다.
시나리오를 보여주기 위해 새 역할(ingress_user_role
)과 새 역할은 액세스할 수 있지만 서비스의 소유자 역할(test_role
)은 액세스할 수 없는 테이블(ingress_user_table
)을 만듭니다. 따라서 서비스에서 서비스 자격 증명을 사용하여 쿼리를 실행하려고 하면 Snowflake는 오류를 반환합니다. 그러나 서비스가 사용자를 대신하여 쿼리를 실행하면 Snowflake가 쿼리를 실행하고 결과를 반환합니다.
역할 및 리소스 만들기¶
이 역할만 액세스할 수 있는 역할(
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;
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' );
서비스가 호출자를 대신하여 테이블을 쿼리하려고 할 때 서비스는 서비스를 생성하는 데 사용된 역할(서비스의 소유자 역할)인
test_role
로만 작업한다는 점에 유의하십시오. 이 역할에는 사용자 테이블에 액세스할 수 있는 권한이 없습니다.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;
기본 웨어하우스 및 기본 보조 역할을 구성합니다.
사용자에 대한 세션이 만들어지면 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;
다음 사항을 참고하십시오.
이 자습서에서는 서비스의 공용 엔드포인트에 로그인합니다. 사용자는
test_role
을 기본 역할로,ingress_user_role
을 보조 역할로 사용합니다. 이렇게 하면 세션에서ingress_user_role
에서 허용하는 모든 작업을 수행할 수 있습니다.기본 역할과 기본 웨어하우스는 서비스가 사용자를 대신하여 세션을 설정할 때 활성화되는 역할과 웨어하우스에만 영향을 줍니다. 호출자의 권한 세션이 설정된 후에는 역할을 변경할 수 없지만 웨어하우스는 변경할 수 있습니다.
서비스 사용 및 호출자 권한 테스트하기¶
서비스가 노출하는 공용 엔드포인트의 URL 을 찾습니다.
SHOW ENDPOINTS IN SERVICE tutorial_db.data_schema.query_service;
응답의
ingress_url
열은 URL을 제공합니다.예
p6bye-myorg-myacct.snowflakecomputing.app
엔드포인트 URL에
/ui
를 추가하고 웹 브라우저에 붙여넣습니다. 그러면 서비스가ui()
함수를 실행합니다(echo_service.py
참조). 엔드포인트 URL 에 처음 접속하면 Snowflake에 로그인하라는 메시지가 표시됩니다. 이 테스트에서는 서비스를 만드는 데 사용한 것과 동일한 사용자를 사용하여 사용자에게 필요한 권한이 있는지 확인하십시오.서비스를 만들 때 사용한 것과 동일한 사용자를 사용합니다. 로그인에 성공하면 웹 서비스에 다음 Web UI 가 표시됩니다.
텍스트 상자에 다음 명령을 입력하고 Enter 키를 누르면 결과를 확인할 수 있습니다.
SELECT * FROM ingress_user_db.ingress_user_schema.ingress_user_table;
설명을 위해 이 자습서의 서비스 코드는 호출자와 서비스 사용자 모두로 쿼리를 실행합니다.
호출자(수신 사용자)를 대신하여 쿼리를 실행합니다. 이 경우 코드는 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.
정리¶
생성한 청구 가능한 리소스를 제거해야 합니다. 자세한 내용은 자습서 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)
코드에서는 다음 사항이 적용됩니다.
ui
함수는 다음 웹 양식을 표시하고 웹 양식에서 제출된 쿼리 요청을 처리합니다.이 함수는
@app.route()
데코레이터를 사용하여/ui
에 대한 요청이 이 함수로 처리되도록 지정합니다.@app.route("/ui", methods=["GET", "POST"]) def ui():
쿼리 서비스는
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
서비스 사용자로 실행. 이 경우 로그인 토큰은 OAuth 토큰뿐입니다.
token = get_login_token()
readiness_probe
함수는@app.get()
데코레이터를 사용하여/healthcheck
에 대한 요청이 이 함수로 처리되도록 지정합니다.@app.get("/healthcheck") def readiness_probe():
이 함수를 사용하면 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"]
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>
서비스 사양¶
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
서비스 사양에서 spec
, capabilities
, serviceRoles
는 최상위 필드입니다.
spec
에서 사양 세부 정보를 제공합니다(서비스 사양 참조 참조). 이 서비스는 공용 웹에서 서비스에 대한 수신 액세스를 허용하는 하나의 공용 엔드포인트(execute
)를 노출한다는 점에 유의하십시오.capabilities
executeAsCaller
기능을 지정합니다. 이는 애플리케이션이 호출자의 권한 을 사용하려고 한다는 것을 Snowflake에게 알려줍니다.serviceRoles
는 USAGE 권한을 부여할 서비스 역할(ui_usage
)과 엔드포인트 이름(execute
)을 지정합니다.readinessProbe
필드는 Snowflake가 준비 상태 프로브에 HTTP GET 요청을 보내 서비스가 트래픽을 처리할 준비가 되었는지 확인하는 데 사용할 수 있는port
및path
를 식별합니다.서비스 코드(
echo_python.py
)는 다음과 같이 준비 상태 프로브를 구현합니다.@app.get("/healthcheck") def readiness_probe():
따라서 사양 파일에는 이에 따라
container.readinessProbe
필드가 포함됩니다.
서비스 사양에 대한 자세한 내용은 서비스 사양 참조 섹션을 참조하십시오.
다음에는 무엇을 해야 합니까?¶
이제 이 자습서를 마쳤으므로 서비스 사용하기 로 돌아가서 다른 항목을 살펴볼 수 있습니다.