Se connecter à une instance Redis depuis une application d'environnement flexible App Engine

Vous pouvez vous connecter à une instance Redis à partir de l'environnement standard App Engine à l'aide de l'accès au VPC sans serveur.

Prérequis

Si vous avez déjà installé le SDK Cloud et créé une instance Redis, vous pouvez ignorer ces étapes.

  1. Installez le SDK Cloud et initialisez :

    gcloud init
    
  2. Suivez le Guide de démarrage rapide pour créer une instance Redis. Retenez la zone, l'adresse IP et le port de l'instance Redis.

Configurer l'accès au VPC sans serveur

Pour vous connecter au réseau VPC autorisé de votre instance Redis à partir de votre application App Engine, vous devez configurer l'accès au VPC sans serveur.

  1. Recherchez le réseau autorisé de votre instance Redis en exécutant la commande suivante :

    gcloud beta redis instances describe [INSTANCE_ID] --region [REGION]
    
  2. Suivez les instructions de la section Créer un connecteur pour créer un connecteur d'accès au VPC sans serveur. Veillez à créer le connecteur dans la même région que votre application et à vérifier qu'il est connecté au réseau VPC autorisé de l'instance Redis. Notez le nom du connecteur.

Exemple d'application

Cet exemple d'application de serveur HTTP établit une connexion à une instance Redis à partir d'une application d'environnement standard App Engine.

Clonez le dépôt correspondant au langage de programmation souhaité et accédez au dossier contenant l'exemple de code :

Go

git clone https://github.com/GoogleCloudPlatform/golang-samples
cd golang-samples/memorystore/redis

Java

git clone https://github.com/GoogleCloudPlatform/java-docs-samples
cd java-docs-samples/memorystore/redis

Node.js

git clone https://github.com/GoogleCloudPlatform/nodejs-docs-samples
cd nodejs-docs-samples/memorystore/redis

Python

git clone https://github.com/GoogleCloudPlatform/python-docs-samples
cd python-docs-samples/memorystore/redis

Cet exemple de code incrémente un compteur Redis à chaque accès au point de terminaison /.

Go

Cette application utilise le client github.com/gomodule/redigo/redis. Installez-le en exécutant la commande suivante :

go get github.com/gomodule/redigo/redis

// Command redis is a basic app that connects to a managed Redis instance.
package main

import (
	"fmt"
	"log"
	"net/http"
	"os"

	"github.com/gomodule/redigo/redis"
)

var redisPool *redis.Pool

func incrementHandler(w http.ResponseWriter, r *http.Request) {
	conn := redisPool.Get()
	defer conn.Close()

	counter, err := redis.Int(conn.Do("INCR", "visits"))
	if err != nil {
		http.Error(w, "Error incrementing visitor counter", http.StatusInternalServerError)
		return
	}
	fmt.Fprintf(w, "Visitor number: %d", counter)
}

func main() {
	redisHost := os.Getenv("REDISHOST")
	redisPort := os.Getenv("REDISPORT")
	redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort)

	const maxConnections = 10
	redisPool = &redis.Pool{
		MaxIdle: maxConnections,
		Dial:    func() (redis.Conn, error) { return redis.Dial("tcp", redisAddr) },
	}

	http.HandleFunc("/", incrementHandler)

	port := os.Getenv("PORT")
	if port == "" {
		port = "8080"
	}
	log.Printf("Listening on port %s", port)
	if err := http.ListenAndServe(":"+port, nil); err != nil {
		log.Fatal(err)
	}
}

Java

Cette application est basée sur un servlet Jetty 3.1.

Il utilise la bibliothèque Jedis :

<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
  <version>3.7.0</version>
</dependency>

La classe AppServletContextListener permet de créer un pool de connexions Redis longue durée :


package com.example.redis;

import java.io.IOException;
import java.util.Properties;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;

@WebListener
public class AppServletContextListener implements ServletContextListener {

  private Properties config = new Properties();

  private JedisPool createJedisPool() throws IOException {
    String host;
    Integer port;
    config.load(
        Thread.currentThread()
            .getContextClassLoader()
            .getResourceAsStream("application.properties"));
    host = config.getProperty("redis.host");
    port = Integer.valueOf(config.getProperty("redis.port", "6379"));

    JedisPoolConfig poolConfig = new JedisPoolConfig();
    // Default : 8, consider how many concurrent connections into Redis you will need under load
    poolConfig.setMaxTotal(128);

    return new JedisPool(poolConfig, host, port);
  }

  @Override
  public void contextDestroyed(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool != null) {
      jedisPool.destroy();
      event.getServletContext().setAttribute("jedisPool", null);
    }
  }

  // Run this before web application is started
  @Override
  public void contextInitialized(ServletContextEvent event) {
    JedisPool jedisPool = (JedisPool) event.getServletContext().getAttribute("jedisPool");
    if (jedisPool == null) {
      try {
        jedisPool = createJedisPool();
        event.getServletContext().setAttribute("jedisPool", jedisPool);
      } catch (IOException e) {
        // handle exception
      }
    }
  }
}

La classe VisitCounterServlet est un servlet Web qui incrémente un compteur Redis :


package com.example.redis;

import java.io.IOException;
import java.net.SocketException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

@WebServlet(name = "Track visits", value = "")
public class VisitCounterServlet extends HttpServlet {

  @Override
  public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    try {
      JedisPool jedisPool = (JedisPool) req.getServletContext().getAttribute("jedisPool");

      if (jedisPool == null) {
        throw new SocketException("Error connecting to Jedis pool");
      }
      Long visits;

      try (Jedis jedis = jedisPool.getResource()) {
        visits = jedis.incr("visits");
      }

      resp.setStatus(HttpServletResponse.SC_OK);
      resp.getWriter().println("Visitor counter: " + String.valueOf(visits));
    } catch (Exception e) {
      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
    }
  }
}

Node.js

Cette application utilise le module redis.

{
  "name": "memorystore-redis",
  "description": "An example of using Memorystore(Redis) with Node.js",
  "version": "0.0.1",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Google Inc.",
  "engines": {
    "node": ">=8.0.0"
  },
  "dependencies": {
    "redis": "^3.0.0"
  }
}

'use strict';
const http = require('http');
const redis = require('redis');

const REDISHOST = process.env.REDISHOST || 'localhost';
const REDISPORT = process.env.REDISPORT || 6379;

const client = redis.createClient(REDISPORT, REDISHOST);
client.on('error', err => console.error('ERR:REDIS:', err));

// create a server
http
  .createServer((req, res) => {
    // increment the visit counter
    client.incr('visits', (err, reply) => {
      if (err) {
        console.log(err);
        res.status(500).send(err.message);
        return;
      }
      res.writeHead(200, {'Content-Type': 'text/plain'});
      res.end(`Visitor number: ${reply}\n`);
    });
  })
  .listen(8080);

Python

Cette application utilise Flask pour la diffusion Web et le package redis-py pour communiquer avec l'instance Redis.

Flask==2.0.1
gunicorn==20.1.0
redis==3.5.3
import logging
import os

from flask import Flask
import redis

app = Flask(__name__)

redis_host = os.environ.get('REDISHOST', 'localhost')
redis_port = int(os.environ.get('REDISPORT', 6379))
redis_client = redis.StrictRedis(host=redis_host, port=redis_port)

@app.route('/')
def index():
    value = redis_client.incr('counter', 1)
    return 'Visitor number: {}'.format(value)

@app.errorhandler(500)
def server_error(e):
    logging.exception('An error occurred during a request.')
    return """
    An internal error occurred: <pre>{}</pre>
    See logs for full stacktrace.
    """.format(e), 500

if __name__ == '__main__':
    # This is used when running locally. Gunicorn is used to run the
    # application on Google App Engine and Cloud Run.
    # See entrypoint in app.yaml or Dockerfile.
    app.run(host='127.0.0.1', port=8080, debug=True)

Préparer l'application pour le déploiement

Pour accéder à l'instance Redis, vous devez configurer l'application App Engine pour qu'elle utilise votre connecteur d'accès au VPC sans serveur et fournir les informations de connexion de votre instance Redis.

  1. Créez une application App Engine si vous n'en avez pas encore une.

  2. Mettez à jour la configuration de l'application pour spécifier votre connecteur d'accès au VPC sans serveur, ainsi que l'adresse IP et le port de votre instance Redis :

    Go

    Mettez à jour le fichier gae_standard_deployment/app.yaml :

    runtime: go111
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Pour en savoir plus, consultez la page Fichier de configuration app.yaml.

    Java

    Mettez à jour le fichier gae_standard_deployment/appengine-web.xml pour spécifier votre connecteur d'accès au VPC sans serveur :

    <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
      <runtime>java8</runtime>
      <threadsafe>true</threadsafe>
      <vpc-access-connector>
        <name>projects/[PROJECT_ID]/locations/[REGION]/connectors/[CONNECTOR_NAME]</name>
      </vpc-access-connector>
    </appengine-web-app>

    Enfin, mettez à jour le fichier src/main/resources/application.properties avec l'adresse IP et le port de votre instance Redis :

    redis.host=REDIS_HOST_IP
    redis.port=6379

    Pour en savoir plus sur la configuration de votre application, consultez la documentation de référence sur appengine-web.xml.

    Node.js

    Mettez à jour le fichier gae_standard_deployment/app.yaml :

    runtime: nodejs10
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Pour en savoir plus, consultez la page Fichier de configuration app.yaml.

    Python

    Mettez à jour le fichier gae_standard_deployment/app.yaml :

    runtime: python37
    entrypoint: gunicorn -b :$PORT main:app
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Serverless VPC Access connector details
    vpc_access_connector:
      name: 'projects/<PROJECT_ID>/locations/<REGION>/connectors/<CONNECTOR_NAME>'

    Pour en savoir plus, consultez la page Fichier de configuration app.yaml.

Déployer l'application dans l'environnement standard App Engine

Pour déployer l'application, procédez comme suit :

  1. Copiez les fichiers de configuration nécessaires dans le répertoire source :

    Go

    Copiez les fichiers app.yaml et go.mod dans le répertoire source :

    cp gae_standard_deployment/{app.yaml,go.mod} .
    

    Java

    Copiez le fichier appengine-web.xml dans le répertoire source :

    mkdir -p src/main/webapp/WEB-INF
    cp gae_standard_deployment/appengine-web.xml src/main/webapp/WEB-INF/
    

    Node.js

    Copiez le fichier app.yaml dans le répertoire source :

    cp gae_standard_deployment/app.yaml .
    

    Python

    Copiez le fichier app.yaml dans le répertoire source :

    cp gae_standard_deployment/app.yaml .
    
  2. Exécutez la commande deploy :

    Go

    gcloud app deploy
    

    Java

    mvn package appengine:stage
    gcloud app deploy target/appengine-staging/app.yaml
    

    Node.js

    gcloud app deploy
    

    Python

    gcloud app deploy
    

Une fois le déploiement terminé, la commande génère l'URL vers laquelle vous pouvez accéder à votre application. Si vous consultez cette URL, le nombre d'instances Redis augmente à chaque chargement de la page.