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

Pour pouvoir accéder à une instance Redis, les applications App Engine doivent se trouver dans la même région et sur le même réseau qu'elle.

Configuration

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.

Exemple d'application

Cet exemple d'application de serveur HTTP établit une connexion à une instance Redis à partir d'une instance d'environnement flexible 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.NewPool(func() (redis.Conn, error) {
		return redis.Dial("tcp", redisAddr)
	}, maxConnections)

	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.3.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==1.1.2
gunicorn==20.0.4
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, l'instance App Engine doit être déployée sur le même réseau que l'instance Redis et vous devez fournir les informations de connexion de votre instance Redis. Vous pouvez trouver le réseau, l'adresse IP et le port autorisés de votre instance Redis en exécutant la commande suivante :

 gcloud redis instances describe [INSTANCE_ID] --region [REGION]
  1. Créez une application App Engine dans la même région que l'instance Redis.

  2. Mettez à jour la configuration de l'application pour spécifier l'adresse IP, le port et le réseau de votre instance Redis :

    Go

    Mettre à jour le fichier gae_flex_deployment/app.yaml :

    runtime: go
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Pour en savoir plus, consultez la page Configurer une application à l'aide d'un fichier app.yaml.

    Java

    Mettez à jour le fichier gae_flex_deployment/app.yaml pour spécifier le réseau de votre instance Redis :

    runtime: java
    env: flex
    
    # Update with Redis instance network name
    network:
      name: default

    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 manière de configurer votre application, consultez la page Configurer votre application avec app.yaml.

    Node.js

    Mettre à jour le fichier gae_flex_deployment/app.yaml :

    runtime: nodejs
    env: flex
    
    # Update with Redis instance details
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Pour en savoir plus, consultez la page Configurer une application à l'aide d'un fichier app.yaml.

    Python

    Mettre à jour le fichier gae_flex_deployment/app.yaml :

    runtime: python
    env: flex
    entrypoint: gunicorn -b :$PORT main:app
    
    runtime_config:
      python_version: 3
    
    # Update with Redis instance IP and port
    env_variables:
      REDISHOST: '<REDIS_IP>'
      REDISPORT: '6379'
    
    # Update with Redis instance network name
    network:
      name: default

    Pour en savoir plus, consultez la page Configurer une application à l'aide d'un fichier app.yaml.

Déployer l'application dans l'environnement flexible 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 le fichier app.yaml dans le répertoire source :

    cp gae_flex_deployment/app.yaml .
    

    Java

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

    mkdir -p src/main/appengine
    cp gae_flex_deployment/app.yaml src/main/appengine/
    

    Node.js

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

    cp gae_flex_deployment/app.yaml .
    

    Python

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

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

    Go

    gcloud app deploy
    

    Cela peut prendre quelques minutes.

    Java

    mvn appengine:deploy
    

    Cela peut prendre quelques minutes.

    Node.js

    gcloud app deploy
    

    Cela peut prendre quelques minutes.

    Python

    gcloud app deploy
    

    Cela peut prendre quelques minutes.

Une fois le déploiement terminé, accédez à votre application à l'URL suivante, en remplaçant [PROJECT_ID] par votre ID de projet Google Cloud :

https://[PROJECT_ID].appspot.com

Le nombre d'instances Redis augmente à chaque visite de l'application.