Connessione a un'istanza Redis da un cluster Google Kubernetes Engine

Mantieni tutto organizzato con le raccolte Salva e classifica i contenuti in base alle tue preferenze.

Puoi connetterti all'istanza Redis solo dai cluster Google Kubernetes Engine che utilizzano la stessa rete autorizzata dell'istanza Redis.

Configurazione

Se hai già installato Google Cloud CLI e creato un'istanza Redis, puoi saltare questi passaggi.

  1. Installa l'interfaccia a riga di comando gcloud e inizializza:

    gcloud init
    
  2. Segui la guida rapida per creare un'istanza Redis. Prendi nota della zona, dell'indirizzo IP e della porta dell'istanza Redis.

Preparazione del cluster GKE in corso...

  1. Se non hai creato un cluster GKE, creane uno utilizzando i seguenti comandi per Google Cloud CLI:

    1. Definisci il progetto per questa applicazione di esempio in gcloud.

      gcloud config set project [PROJECT_ID]

    2. Imposta la variabile di configurazione della zona di Compute Engine in gcloud.

      gcloud config set compute/zone [ZONE]

    3. Crea un cluster GKE denominato visitcount-cluster.

      gcloud container clusters create visitcount-cluster --num-nodes=3 --enable-ip-alias

  2. Se non hai creato il cluster tramite gcloud, utilizza questo comando per recuperare le credenziali del cluster:

    gcloud container clusters get-credentials [CLUSTER_NAME] --zone [CLUSTER_ZONE] --project [PROJECT_ID]
    
    1. CLUSTER_NAME è il nome del cluster GKE.
    2. CLUSTER_ZONE è la zona in cui si trova il tuo cluster.
    3. PROJECT_ID è il progetto in cui esistono il cluster e le istanze Redis.
  3. Se il cluster è versione 1.8 o successiva e sono abilitati gli alias IP, salta questo passaggio. Se nel tuo cluster è installata la versione 1.7 o precedente oppure se nel cluster della versione 1.8 o successiva non sono abilitati gli alias IP, segui questi passaggi alternativi prima di provare a connetterti all'istanza.

    1. Esegui questi comandi, sostituendo REserveED_IP_RANGE con l'intervallo IP riservato della tua istanza:

      git clone https://github.com/bowei/k8s-custom-iptables.git
      cd k8s-custom-iptables/
      TARGETS="RESERVED_IP_RANGE" ./install.sh
      cd ..
      
    2. Se non conosci l'intervallo IP riservato della tua istanza, puoi scoprirlo tramite la console (opzioni avanzate) o inserendo questo comando:

      gcloud redis instances describe INSTANCE_ID --region=REGION
      

    Per ulteriori informazioni sugli alias IP, inclusa la creazione di un cluster con questa impostazione abilitata, consulta la documentazione relativa agli alias IP.

Applicazione di esempio

Questa applicazione server HTTP di esempio stabilisce una connessione a un'istanza Redis da un cluster Google Kubernetes Engine.

Clona il repository per il linguaggio di programmazione desiderato e vai alla cartella che contiene il codice di esempio:

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

Questa applicazione di esempio incrementa un contatore Redis a ogni accesso all'endpoint /.

Go

Questa applicazione utilizza il client github.com/gomodule/redigo/redis. Installala eseguendo il comando seguente:

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

Questa applicazione è basata su servlet Jetty 3.1.

Utilizza la libreria Jedis:

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

La classe AppServletContextListener viene utilizzata per creare un pool di connessione Redis di lunga durata:


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 è un servlet web che incrementa un contatore 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

Questa applicazione utilizza il modulo 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": ">=12.0.0"
  },
  "dependencies": {
    "redis": "^4.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

Questa applicazione utilizza Flask per la pubblicazione web e il pacchetto redis-py per comunicare con l'istanza Redis.

Flask==2.1.0
gunicorn==20.1.0
redis==4.1.1
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)

Crea l'immagine container

Crea ed esegui il push dell'immagine container su Container Registry:

cp gke_deployment/Dockerfile .
export PROJECT_ID="$(gcloud config get-value project -q)"
docker build -t gcr.io/${PROJECT_ID}/visit-counter:v1 .
gcloud docker -- push gcr.io/${PROJECT_ID}/visit-counter:v1

Deployment dell'applicazione in Google Kubernetes Engine

Aggiorna gke_deployment/visit-counter.yaml sostituendo <PROJECT_ID> con il tuo ID progetto Google Cloud. Questo file contiene la configurazione per il deployment e il servizio.

Go

Per evitare l'impostazione come hardcoded dell'IP dell'istanza Redis, puoi creare un oggetto ConfigMap redishost:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il seguente comando:

    kubectl get configmaps redishost -o yaml
# Copyright 2019 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Java

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Node.js

Per evitare l'impostazione come hardcoded dell'IP dell'istanza Redis, puoi creare un oggetto ConfigMap redishost:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il seguente comando:

    kubectl get configmaps redishost -o yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT_ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Python

Per evitare l'impostazione come hardcoded dell'IP dell'istanza Redis, puoi creare un oggetto ConfigMap redishost:

    export REDISHOST_IP=XXX.XXX.XXX.XXX
    kubectl create configmap redishost --from-literal=REDISHOST=${REDISHOST_IP}

Verifica la configurazione utilizzando il seguente comando:

    kubectl get configmaps redishost -o yaml
# Copyright 2021 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: visit-counter
  labels:
    app: visit-counter
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: visit-counter
    spec:
      containers:
      - name: visit-counter
        image: "gcr.io/<PROJECT-ID>/visit-counter:v1"
        env:
        - name: REDISHOST
          valueFrom:
            configMapKeyRef:
              name: redishost
              key: REDISHOST
        ports:
        - name: http
          containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: visit-counter
spec:
  type: LoadBalancer
  selector:
    app: visit-counter
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP

Applica la configurazione al tuo cluster:

    kubectl apply -f gke_deployment/visit-counter.yaml

Determina l'indirizzo [EXTERNAL-IP] per questa app di esempio eseguendo questo comando:

    kubectl get service visit-counter

Visualizza la tua app ospitata all'indirizzo http://[EXTERNAL-IP] tramite il tuo browser oppure invia una richiesta GET tramite cURL o il tuo browser:

    curl http://[EXTERNAL-IP]

Rimozione della voce delle tabelle IP per l'istanza Redis

Se hai seguito il passaggio tre della sezione di questa procedura dettagliata denominata Preparare il cluster GKE, hai installato l'intervallo IP riservato della tua istanza Redis alle tabelle IP della tua istanza GKE. Se vuoi rimuovere questa voce di intervallo IP Redis dalle tabelle IP della tua istanza GKE, esegui il comando seguente dalla directory k8s-custom-iptables/:

    ./uninstall.sh