Tutoriel 1 : créer un service Snowpark Container Services

Introduction

Après avoir terminé l’installation commune, vous êtes prêt à créer un service. Dans ce tutoriel, vous créez un service (nommé echo_service) qui renvoie simplement un texte que vous fournissez en entrée. Par exemple, si la chaîne d’entrée est « Hello World », le service renvoie « I said, Hello World ».

Ce tutoriel comporte deux parties :

Partie 1 : créez et testez un service. Vous téléchargez le code fourni pour ce tutoriel et suivez les instructions étape par étape :

  1. Téléchargez le code de service pour ce tutoriel.

  2. Créez une image Docker pour Snowpark Container Services, et chargez l’image dans un référentiel de votre compte.

  3. Créez un service, en fournissant le fichier de spécification de service et le pool de calcul dans lequel le service sera exécuté.

  4. Créez une fonction de service pour communiquer avec le service.

  5. Utilisez le service. Vous envoyez des demandes echo au service et vérifiez la réponse.

Partie 2 : comprendre le service. Cette section donne une vue d’ensemble du code de service et met en évidence la façon dont les différentes composantes collaborent.

1 : Télécharger le code de service

Un code (une application Python) est fourni pour créer le service echo.

  1. Téléchargez le fichier zip dans un répertoire.

  2. Décompressez le contenu, qui comprend un répertoire pour chaque tutoriel. Le répertoire Tutorial-1 contient les fichiers suivants :

    • Dockerfile

    • echo_service.py

    • templates/basic_ui.html

2 : Construire une image et la charger

Construisez une image pour la plateforme linux/amd64 prise en charge par Snowpark Container Services, puis chargez l’image dans le référentiel d’images de votre compte (voir Configuration commune).

Vous aurez besoin d’informations sur le référentiel (l’URL du référentiel et le nom d’hôte du registre) avant de pouvoir construire et charger l’image. Pour plus d’informations, voir Registre et référentiels.

Obtenir des informations sur le référentiel

  1. Pour obtenir l’URL du référentiel, exécutez la commande SQL SHOW IMAGE REPOSITORIES.

    SHOW IMAGE REPOSITORIES;
    
    Copy
    • La colonne repository_url de la sortie fournit l’URL. En voici un exemple :

      <orgname>-<acctname>.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository
      
    • Le nom d’hôte dans l’URL du référentiel est le nom d’hôte du registre. En voici un exemple :

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

Construire l’image et la charger dans le référentiel

  1. Ouvrez une fenêtre de terminal et accédez au répertoire contenant les fichiers que vous avez décompressés.

  2. Pour créer une image Docker, exécutez la commande docker build suivante à l’aide de la CLI Docker. Notez que la commande spécifie le répertoire de travail actuel (.) comme PATH pour les fichiers à utiliser pour la construction de l’image.

    docker build --rm --platform linux/amd64 -t <repository_url>/<image_name> .
    
    Copy
    • Pour image_name, utilisez my_echo_service_image:latest.

    Exemple

    docker build --rm --platform linux/amd64 -t myorg-myacct.registry.snowflakecomputing.com/tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest .
    
    Copy
  3. Chargez l’image dans le référentiel de votre compte Snowflake. Pour que Docker puisse charger une image en votre nom dans votre référentiel, vous devez d’abord authentifier Docker avec Snowflake.

    1. Pour authentifier Docker auprès du registre Snowflake, exécutez la commande suivante.

      docker login <registry_hostname> -u <username>
      
      Copy
      • Pour username, indiquez votre nom d’utilisateur Snowflake. Docker vous demandera votre mot de passe.

    2. Pour charger l’image, exécutez la commande suivante :

      docker push <repository_url>/<image_name>
      
      Copy

      Exemple

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

3 : Créer un service

Dans cette section, vous créez un service et une fonction de service pour communiquer avec le service.

Pour créer un service, vous devez disposer des éléments suivants :

  • Un pool de calcul. Snowflake exécute votre service dans le pool de calcul spécifié. Vous avez créé un pool de calcul dans le cadre de la configuration commune.

  • Une spécification de service. Cette spécification fournit à Snowflake les informations nécessaires pour configurer et exécuter votre service. Pour plus d’informations, voir Snowpark Container Services : utilisation des tâches. Dans ce tutoriel, vous fournissez la spécification en ligne, dans la commande CREATE SERVICE. Vous pouvez également enregistrer la spécification dans un fichier de votre zone de préparation Snowflake et fournir des informations sur le fichier dans la commande CREATE SERVICE, comme le montre le tutoriel 2.

Une fonction de service est l’une des méthodes disponibles pour communiquer avec votre service. Une fonction de service est une fonction définie par l’utilisateur (UDF) que vous associez au point de terminaison de service. Lorsque la fonction de service est exécutée, elle envoie une requête au point de terminaison de service et reçoit une réponse.

  1. Vérifiez que le pool de calcul est prêt et que vous êtes dans le bon contexte pour créer le service.

    1. Auparavant, vous avez défini le contexte dans l’étape Configuration commune. Pour vous assurer que vous êtes dans le bon contexte pour les instructions SQL dans cette étape, exécutez ce qui suit :

    USE ROLE test_role;
    USE DATABASE tutorial_db;
    USE SCHEMA data_schema;
    USE WAREHOUSE tutorial_warehouse;
    
    Copy
    1. Pour s’assurer que le pool de calcul que vous avez créé dans la configuration commune est prêt, exécutez DESCRIBE COMPUTE POOL, et vérifiez que state est ACTIVE ou IDLE. Si le state est STARTING, vous devez attendre que le state devienne ACTIVE ou IDLE.

    DESCRIBE COMPUTE POOL tutorial_compute_pool;
    
    Copy
  2. Pour créer le service, exécutez la commande suivante en utilisant test_role :

    CREATE SERVICE echo_service
      IN COMPUTE POOL tutorial_compute_pool
      FROM SPECIFICATION $$
        spec:
          containers:
          - name: echo
            image: /tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest
            env:
              SERVER_PORT: 8000
              CHARACTER_NAME: Bob
            readinessProbe:
              port: 8000
              path: /healthcheck
          endpoints:
          - name: echoendpoint
            port: 8000
            public: true
          $$
       MIN_INSTANCES=1
       MAX_INSTANCES=1;
    
    Copy

    Note

    Si un service portant ce nom existe déjà, utilisez la commande DROP SERVICE pour supprimer le service précédemment créé, puis créez ce service.

  3. Exécutez les commandes SQL suivantes pour obtenir des informations détaillées sur le service que vous venez de créer. Pour plus d’informations, voir Snowpark Container Services : utilisation des tâches.

    • Pour dresser la liste des services de votre compte, exécutez la commande SHOW SERVICES :

      SHOW SERVICES;
      
      Copy
    • Pour connaître le statut de votre service, appelez la fonction système SYSTEM$GET_SERVICE_STATUS :

      SELECT SYSTEM$GET_SERVICE_STATUS('echo_service');
      
      Copy
    • Pour obtenir des informations sur votre service, exécutez la commande DESCRIBE SERVICE :

      DESCRIBE SERVICE echo_service;
      
      Copy
  4. Pour créer une fonction de service, exécutez la commande suivante :

    CREATE FUNCTION my_echo_udf (InputText varchar)
      RETURNS varchar
      SERVICE=echo_service
      ENDPOINT=echoendpoint
      AS '/echo';
    
    Copy

    Remarques :

    • La propriété SERVICE associe l’UDF au service echo_service.

    • La propriété ENDPOINT associe l’UDF au point de terminaison echoendpoint au sein du service.

    • AS “/echo” indique le chemin d’accès HTTP au serveur echo. Vous trouverez ce chemin dans le code de service (echo_service.py).

4 : Utiliser le service

Tout d’abord, pour définir le contexte des instructions SQL de cette section, exécutez ce qui suit :

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

Vous pouvez maintenant communiquer avec le service echo.

  1. Utilisation d’une fonction de service : vous pouvez appeler la fonction de service dans une requête. La fonction de service de l’exemple (my_echo_udf) peut prendre en entrée une seule chaîne ou une liste de chaînes.

    Exemple 1.1 : transmettre une seule chaîne

    • Pour appeler la fonction de service my_echo_udf , exécutez l’instruction SELECT suivante, en transmettant une chaîne d’entrée ('hello') :

      SELECT my_echo_udf('hello!');
      
      Copy

      Snowflake envoie une requête POST au point de terminaison de service (echoendpoint). À la réception de la requête, le service répercute la chaîne d’entrée dans la réponse.

      +--------------------------+
      | **MY_ECHO_UDF('HELLO!')**|
      |------------------------- |
      | Bob said hello!          |
      +--------------------------+
      

    Exemple 1.2 : transmettre une liste de chaînes

    Lorsque vous transmettez une liste de chaînes à la fonction de service, Snowflake regroupe ces chaînes d’entrée et envoie une série de requêtes POST au service. Une fois que le service a traité toutes les chaînes, Snowflake combine les résultats et les renvoie.

    L’exemple suivant transmet une colonne de table à la fonction de service.

    1. Créez une table avec plusieurs chaînes :

      CREATE TABLE messages (message_text VARCHAR)
        AS (SELECT * FROM (VALUES ('Thank you'), ('Hello'), ('Hello World')));
      
      Copy
    2. Pour vérifier que la table a été créée :

      SELECT * FROM messages;
      
      Copy
    3. Pour appeler la fonction de service, exécutez l’instruction SELECT suivante, en transmettant les lignes de la table en entrée :

      SELECT my_echo_udf(message_text) FROM messages;
      
      Copy

      Sortie :

      +---------------------------+
      | MY_ECHO_UDF(MESSAGE_TEXT) |
      |---------------------------|
      | Bob said Thank you        |
      | Bob said Hello            |
      | Bob said Hello World      |
      +---------------------------+
      
  2. En utilisant un navigateur Web : le service expose le point de terminaison publiquement (voir la spécification en ligne fournie dans la commande CREATE SERVICE). Par conséquent, vous pouvez vous connecter à une UI Web que le service expose à Internet, puis envoyer des requêtes au service à partir d’un navigateur Web.

    1. Trouvez l’URL du point de terminaison public auquel le service est exposé :

      SHOW ENDPOINTS IN SERVICE echo_service;
      
      Copy

      La colonne ingress_url de la réponse fournit l’URL.

      Exemple

      p6bye-myorg-myacct.snowflakecomputing.app
      
    2. Ajoutez /ui à l’URL du point de terminaison et collez-la dans le navigateur Web. Le service exécute alors la fonction ui() (voir echo_service.py).

      Notez que la première fois que vous accédez à l’URL du point de terminaison, il vous sera demandé de vous connecter à Snowflake. Pour ce test, utilisez le même utilisateur que celui que vous avez utilisé pour créer le service afin de vous assurer qu’il dispose des privilèges nécessaires.

      Web form to communicate with echo service.
    3. Saisissez la chaîne « Hello » dans la case Entrée et appuyez sur Retour.

      Web form showing response from the Echo service.

5 : Nettoyage

Si vous ne prévoyez pas de poursuivre le tutoriel 2 ou le tutoriel 3, vous devez supprimer les ressources facturables que vous avez créées. Pour plus d’informations, voir l’étape 5 dans le tutoriel 3.

6 : Vérification du code de service

Cette section couvre les sujets suivants :

Examen du code du tutoriel 1

Le fichier zip que vous avez chargé à l’étape 1 comprend les fichiers suivants :

  • Dockerfile

  • echo_service.py

  • templates/basic_ui.html

Vous utilisez également la spécification de service lors de la création du service. La section suivante explique comment ces composants de code fonctionnent ensemble pour créer le service.

Fichier echo_service.py

Ce fichier Python contient le code qui met en œuvre un serveur HTTP minimal qui renvoie (retour echo) le texte saisi. Le code effectue principalement deux tâches : traiter les requêtes echo des fonctions de service Snowflake et fournir une interface utilisateur Web (UI) pour soumettre des requêtes echo.

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

SERVICE_HOST = os.getenv('SERVER_HOST', '0.0.0.0')
SERVER_PORT = os.getenv('SERVER_PORT', 8080)
CHARACTER_NAME = os.getenv('CHARACTER_NAME', 'I')


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


logger = get_logger('echo-service')

app = Flask(__name__)


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


@app.post("/echo")
def echo():
  '''
  Main handler for input data sent by Snowflake.
  '''
  message = request.json
  logger.debug(f'Received request: {message}')

  if message is None or not message['data']:
    logger.info('Received empty message')
    return {}

  # input format:
  #   {"data": [
  #     [row_index, column_1_value, column_2_value, ...],
  #     ...
  #   ]}
  input_rows = message['data']
  logger.info(f'Received {len(input_rows)} rows')

  # output format:
  #   {"data": [
  #     [row_index, column_1_value, column_2_value, ...}],
  #     ...
  #   ]}
  output_rows = [[row[0], get_echo_response(row[1])] for row in input_rows]
  logger.info(f'Produced {len(output_rows)} rows')

  response = make_response({"data": output_rows})
  response.headers['Content-type'] = 'application/json'
  logger.debug(f'Sending response: {response.json}')
  return response


@app.route("/ui", methods=["GET", "POST"])
def ui():
  '''
  Main handler for providing a web UI.
  '''
  if request.method == "POST":
    # getting input in HTML form
    input_text = request.form.get("input")
    # display input and output
    return render_template("basic_ui.html",
      echo_input=input_text,
      echo_reponse=get_echo_response(input_text))
  return render_template("basic_ui.html")


def get_echo_response(input):
  return f'{CHARACTER_NAME} said {input}'

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

Dans le code :

  • La fonction echo permet à une fonction de service Snowflake de communiquer avec le service. Cette fonction spécifie la décoration @app.post() comme indiqué :

    @app.post("/echo")
    def echo():
    
    Copy

    Lorsque le serveur echo reçoit votre requête HTTP POST avec le chemin /echo , il achemine la requête vers cette fonction. La fonction s’exécute et renvoie les chaînes du corps de la requête dans la réponse.

    Pour prendre en charge la communication à partir d’une fonction de service Snowflake, ce serveur met en œuvre les fonctions externes. En d’autres termes, la mise en œuvre du serveur suit un certain format de données d’entrée/sortie afin de servir une fonction SQL, et il s’agit du même format de données d’entrée/de sortie utilisé par les fonctions externes.

  • La section de fonction ui du code affiche un formulaire Web et traite les requêtes echo soumises à partir du formulaire Web. Cette fonction utilise le décorateur @app.route() pour spécifier que les requêtes pour /ui sont traitées par cette fonction :

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

    Le service echo expose publiquement le point de terminaison echoendpoint (voir la spécification du service), ce qui permet de communiquer avec le service sur le Web. Lorsque vous chargez l’URL du point de terminaison public avec /ui ajouté dans votre navigateur, celui-ci envoie une requête HTTP GET pour ce chemin, et le serveur achemine la requête vers cette fonction. La fonction s’exécute et renvoie un formulaire HTML simple dans lequel l’utilisateur peut saisir une chaîne.

    Une fois que l’utilisateur a saisi une chaîne et soumis le formulaire, le navigateur envoie une requête HTTP post pour ce chemin, et le serveur achemine la requête vers cette même fonction. La fonction s’exécute et renvoie une réponse HTTP contenant la chaîne d’origine.

  • La fonction readiness_probe utilise le décorateur @app.get() pour spécifier que les requêtes pour /healthcheck sont traitées par cette fonction :

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

    Cette fonction permet à Snowflake de vérifier l’état de préparation du service. Lorsque le conteneur démarre, Snowflake veut confirmer que l’application fonctionne et que le service est prêt à répondre aux requêtes. Snowflake envoie une requête HTTP GET avec ce chemin (en tant que sonde de santé, probe readiness) pour s’assurer que seuls les conteneurs sains servent le trafic. La fonction peut faire ce que vous voulez.

  • La fonction get_logger permet de mettre en place la journalisation.

Dockerfile

Ce fichier contient toutes les commandes pour construire une image en utilisant Docker.

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

Le Dockerfile contient des instructions pour installer la bibliothèque Flask dans le conteneur Docker. Le code de echo_service.py s’appuie sur la bibliothèque Flask pour traiter les requêtes HTTP.

/template/basic_ui.html

Le service echo expose publiquement le point de terminaison echoendpoint (voir la spécification du service), ce qui permet de communiquer avec le service sur le Web. Lorsque vous chargez l’URL du point de terminaison public avec /ui en annexe dans votre navigateur, le service echo affiche ce formulaire. Vous pouvez saisir une chaîne dans le formulaire et soumettre le formulaire, et le service renvoie la chaîne dans une réponse HTTP.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Welcome to echo service!</title>
  </head>
  <body>
    <h1>Welcome to echo service!</h1>
    <form action="{{ url_for("ui") }}" method="post">
      <label for="input">Input:<label><br>
      <input type="text" id="input" name="input"><br>
    </form>
    <h2>Input:</h2>
    {{ echo_input }}
    <h2>Output:</h2>
    {{ echo_reponse }}
  </body>
</html>
Copy

Spécification du service

Snowflake utilise les informations que vous fournissez dans cette spécification pour configurer et faire fonctionner votre service.

spec:
  containers:
  - name: echo
    image: /tutorial_db/data_schema/tutorial_repository/my_echo_service_image:latest
    env:
      SERVER_PORT: 8000
      CHARACTER_NAME: Bob
    readinessProbe:
      port: 8000
      path: /healthcheck
  endpoints:
  - name: echoendpoint
    port: 8000
    public: true
Copy

Dans la spécification de service :

  • Le containers.image spécifie l’image pour que Snowflake démarre un conteneur.

  • Le champ facultatif endpoints spécifie le point de terminaison auquel le service est exposé.

    • Le name spécifie un nom convivial pour le port réseau TCP sur lequel le conteneur est à l’écoute. Vous utilisez ce nom convivial de point de terminaison pour envoyer des requêtes au port correspondant. Notez que le env.SERVER_PORT contrôle ce numéro de port.

    • Le point de terminaison est également configuré comme public. Cela permet au trafic d’accéder à ce point de terminaison depuis le Web public.

  • Le champ facultatif containers.env est ajouté pour illustrer la façon dont vous pouvez remplacer les variables d’environnement que Snowflake transmet à tous les processus de votre conteneur. Par exemple, le code de service (echo_service.py) lit les variables d’environnement avec les valeurs par défaut comme indiqué :

    CHARACTER_NAME = os.getenv('CHARACTER_NAME', 'I')
    SERVER_PORT = os.getenv('SERVER_PORT', 8080)
    
    Copy

    Il fonctionne de la manière suivante :

    • Lorsque le service echo reçoit une requête HTTP POST avec une chaîne (par exemple, « Hello ») dans le corps de la requête, le service renvoie « I said Hello » par défaut. Le code utilise la variable d’environnement CHARACTER_NAME pour déterminer le mot qui précède « said ». Par défaut, CHARACTER_NAME est défini sur « I ».

      Vous pouvez remplacer la valeur par défaut CHARACTER_NAME dans la spécification de service. Par exemple, si vous définissez la valeur sur « Bob », le service echo renvoie une réponse « Bob said Hello ».

    • De la même manière, la spécification du service remplace le port par défaut (SERVER_PORT) sur lequel le service écoute, qui est 8080, par 8000.

  • Le champ readinessProbe identifie les port et path que Snowflake peut utiliser pour envoyer une requête HTTP GET à la probe readiness afin de vérifier que le service est prêt à gérer le trafic.

    Le code de service (echo_python.py) met en œuvre la probe readiness comme suit :

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

    Le fichier de spécification inclut donc le champ container.readinessProbe en conséquence.

Pour plus d’informations sur les spécifications de service, voir Référence Spécification de service.

Comprendre la fonction de service

Une fonction de service est l’une des méthodes utilisées pour communiquer avec votre service (voir Utilisation d’un service). Une fonction de service est une fonction définie par l’utilisateur (UDF) que vous associez à un point de terminaison de service. Lorsque la fonction de service est exécutée, elle envoie une requête au point de terminaison de service associé et reçoit une réponse.

Vous créez la fonction de service suivante en exécutant la commande CREATE FUNCTION avec les paramètres suivants :

CREATE FUNCTION my_echo_udf (InputText VARCHAR)
  RETURNS VARCHAR
  SERVICE=echo_service
  ENDPOINT=echoendpoint
  AS '/echo';
Copy

Remarques :

  • La fonction my_echo_udf prend une chaîne en entrée et renvoie une chaîne.

  • La propriété SERVICE identifie le service (echo_service) et la propriété ENDPOINT identifie le nom convivial du point de terminaison (echoendpoint).

  • Le AS “/echo” spécifie le chemin d’accès au service. Dans echo_service.py, le décorateur @app.post associe ce chemin à la fonction echo.

Cette fonction se connecte à l’élément ENDPOINT spécifique de l’élément SERVICE spécifié. Lorsque vous appelez cette fonction, Snowflake envoie une requête au chemin /echo dans le conteneur de service.

Construire et tester une image localement

Vous pouvez tester l’image Docker localement avant de la charger vers un référentiel dans votre compte Snowflake. Dans les tests locaux, votre conteneur fonctionne de manière autonome (il ne s’agit pas d’un service exécuté par Snowflake).

Pour tester l’image Docker du tutoriel 1 :

  1. Pour créer une image Docker, dans la CLI Docker, exécutez la commande suivante :

    docker build --rm -t my_service:local .
    
    Copy
  2. Pour lancer votre code, exécutez la commande suivante :

    docker run --rm -p 8080:8080 my_service:local
    
    Copy
  3. Envoyez une requête echo au service en utilisant l’une des méthodes suivantes :

    • Utilisation de la commande cURL :

      Dans une autre fenêtre de terminal, en utilisant cURL, envoyez la requête POST suivante au port 8080 :

      curl -X POST http://localhost:8080/echo \
        -H "Content-Type: application/json" \
        -d '{"data":[[0, "Hello friend"], [1, "Hello World"]]}'
      
      Copy

      Notez que le corps de la requête comprend deux chaînes. Cette commande cURL envoie une requête POST au port 8080 sur lequel le service est à l’écoute. Le 0 dans les données est l’indice de la chaîne d’entrée dans la liste. Le service echo répercute les chaînes saisies en réponse, comme indiqué :

      {"data":[[0,"I said Hello Friend"],[1,"I said Hello World"]]}
      
    • En utilisant un navigateur Web :

      1. Dans votre navigateur, sur le même ordinateur, ouvrez http://localhost:8080/ui.

        Cette opération envoie une requête GET au port 8080, sur lequel le service est à l’écoute. Le service exécute la fonction ui() , qui rend un formulaire HTML comme indiqué :

        Web form to communicate with echo service.
      2. Saisissez la chaîne « Hello » dans la case Entrée et appuyez sur Retour.

        Web form showing response from the Echo service.

Quelle est la prochaine étape ?

Vous pouvez maintenant tester le tutoriel 2 qui exécute une tâche.