從 Google Kubernetes Engine 叢集連線至 Redis 執行個體

您只能從與 Redis 執行個體使用相同授權網路的 Google Kubernetes Engine 叢集連線至 Redis 執行個體。

設定

如果您已安裝 Google Cloud CLI 並建立 Redis 執行個體,可以略過這些步驟。

  1. 安裝 gcloud CLI 並初始化:

    gcloud init 
  2. 按照快速入門指南中的說明建立 Redis 執行個體。請記下 Redis 執行個體的區域、IP 位址和通訊埠。

準備 GKE 叢集

  1. 如果您尚未建立 GKE 叢集,請使用下列 Google Cloud CLI 適用的指令建立一個叢集:

    1. gcloud 中,指定這個應用程式範例的專案。

      gcloud config set project [PROJECT_ID]

    2. gcloud 中設定 Compute Engine 區域設定變數。

      gcloud config set compute/zone [ZONE]

    3. 建立名為 visitcount-cluster 的 GKE 叢集。

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

  2. 如果不是透過 gcloud 建立叢集,請使用下列指令擷取叢集憑證:

    gcloud container clusters get-credentials [CLUSTER_NAME] --zone [CLUSTER_ZONE] --project [PROJECT_ID] 
    1. CLUSTER_NAME 是 GKE 叢集的名稱。
    2. CLUSTER_ZONE 是叢集所在的區域。
    3. PROJECT_ID 是叢集和 Redis 執行個體所在的專案。
  3. 如果您的叢集為 1.8 以上版本,並且已啟用 IP 別名,請略過這個步驟。如果您的叢集為 1.7 以下版本,或是您的叢集為 1.8 以上版本,但未啟用 IP 別名,在嘗試連線到執行個體之前,請先按照這些解決方法的步驟操作。

    1. 執行這些指令,並將 RESERVED_IP_RANGE 取代為執行個體的保留 IP 範圍:

      git clone https://github.com/bowei/k8s-custom-iptables.git cd k8s-custom-iptables/ TARGETS="RESERVED_IP_RANGE" ./install.sh cd .. 
    2. 如果您不知道執行個體的保留 IP 範圍,可以透過主控台的進階選項查看,或是輸入這個指令:

      gcloud redis instances describe INSTANCE_ID --region=REGION 

    如要進一步瞭解 IP 別名,包含如何建立啟用這項設定的叢集,請參閱 IP 別名說明文件

應用程式範例

本 HTTP 伺服器應用程式範例會從 Google Kubernetes Engine 叢集建立與 Redis 執行個體的連線。

複製所需程式語言的存放區,然後前往包含範例程式碼的資料夾:

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 

每次存取 / 端點時,這個範例應用程式都會遞增 Redis 計數器。

Go

這個應用程式會使用 github.com/gomodule/redigo/redis 用戶端。執行下列指令即可安裝:

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

本應用程式以 Jetty 3.1 Servlet 為基礎。

此方法使用 Jedis 程式庫:

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

AppServletContextListener 類別用於建立長期 Redis 連線集區:

 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       }     }   } }

VisitCounterServlet 類別是會遞增 Redis 計數器的網頁 Servlet:

 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

這個應用程式會使用 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": ">=16.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

這個應用程式使用 Flask 提供網頁服務,並使用 redis-py 套件與 Redis 執行個體通訊。

Flask==3.0.3 gunicorn==23.0.0 redis==6.0.0 Werkzeug==3.0.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 f"Visitor number: {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)

建構容器映像檔

建構容器映像檔並推送到 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 

將應用程式部署至 Google Kubernetes Engine

更新 gke_deployment/visit-counter.yaml,將 <PROJECT_ID> 替換為您的 Google Cloud 專案 ID。這個檔案包含部署作業和服務的設定。

Go

如要避免採用硬式編碼的 Redis 執行個體 IP,您可以建立 redishost ConfigMap

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

使用下列指令確認設定:

    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

如要避免採用硬式編碼的 Redis 執行個體 IP,您可以建立 redishost ConfigMap

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

使用下列指令確認設定:

    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

如要避免採用硬式編碼的 Redis 執行個體 IP,您可以建立 redishost ConfigMap

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

使用下列指令確認設定:

    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  

將設定套用到叢集:

    kubectl apply -f gke_deployment/visit-counter.yaml 

執行下列指令,判斷這個應用程式範例的 [EXTERNAL-IP] 位址:

    kubectl get service visit-counter 

透過瀏覽器查看託管在 http://[EXTERNAL-IP] 的應用程式,或是透過 cURL 或瀏覽器傳送 GET 要求:

    curl http://[EXTERNAL-IP] 

移除 Redis 執行個體的 IP 表格項目

如果您按照本逐步操作說明中標題為準備 GKE 叢集一節的第三個步驟操作,則已經將 Redis 執行個體的保留 IP 範圍安裝到 GKE 執行個體的 IP 表格中。如要從 GKE 執行個體的 IP 表中移除這個 Redis IP 範圍項目,請透過 k8s-custom-iptables/ 目錄執行下列指令:

    ./uninstall.sh