Redis 인스턴스와 동일한 승인 네트워크를 사용하는 Compute Engine VM 인스턴스에서 Redis 인스턴스에 연결할 수 있습니다.
설정
Google Cloud CLI를 설치하고, Redis 인스턴스와 Cloud Storage 버킷을 만들었다면 아래 단계를 건너뛸 수 있습니다.
gcloud CLI를 설치하고 초기화합니다.
gcloud init
빠른 시작 가이드에 따라 Redis 인스턴스를 만듭니다. Redis 인스턴스의 영역, IP 주소, 포트를 기록합니다.
이 샘플 애플리케이션의 애플리케이션 아티팩트가 업로드되는 Cloud Storage 버킷을 만듭니다. 자세한 내용은 버킷 만들기를 참조하세요.
샘플 애플리케이션의 gcloud 설정 구성
gcloud
기본 프로젝트를 샘플 앱 프로젝트로 설정합니다.gcloud config set project [PROJECT_ID]
샘플 애플리케이션
이 샘플 HTTP 서버 애플리케이션은 Compute Engine VM 인스턴스에서 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 서블릿 기반입니다.
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 카운터를 증가시키는 웹 서블릿입니다.
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 인스턴스와 통신하기 위해 redis-py
패키지를 사용합니다.
Flask==3.0.3
gunicorn==22.0.0
redis==5.0.1
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)
Compute Engine VM에 애플리케이션 배포
gce_deployment
디렉터리로 이동합니다.
cd gce_deployment
배포 스크립트는 아티팩트를 Cloud Storage 경로에 업로드합니다. 그런 다음 Compute Engine 인스턴스를 시작하여 방화벽을 만들어 포트 8080을 노출합니다. 그러면 시작 스크립트에서 인스턴스를 준비합니다.
다음은 이 애플리케이션을 새로운 Compute Engine VM 인스턴스에 배포하는 샘플 배포 스크립트입니다.
Go
관리형 Redis 인스턴스 IP 및 포트를 가리키도록 환경 변수를 설정합니다.
export REDISHOST=XXX.XXX.XXX.XXX
export REDISPORT=6379
if [ -z "$REDISHOST" ]; then
echo "Must set \$REDISHOST. For example: REDISHOST=127.0.0.1"
exit 1
fi
if [ -z "$REDISPORT" ]; then
echo "Must set \$REDISPORT. For example: REDISPORT=6379"
exit 1
fi
if [ -z "$GCS_BUCKET_NAME" ]; then
echo "Must set \$GCS_BUCKET_NAME. For example: GCS_BUCKET_NAME=my-bucket"
exit 1
fi
if [ -z "$ZONE" ]; then
ZONE=$(gcloud config get-value compute/zone -q)
echo "$ZONE"
fi
# Cross compile the app for linux/amd64
GOOS=linux GOARCH=amd64 go build -v -o app ../main.go
# Add the app binary
tar -cvf app.tar app
# Copy to GCS bucket
gsutil cp app.tar gs://"$GCS_BUCKET_NAME"/gce/
# Create an instance
gcloud compute instances create my-instance \
--image-family=debian-9 \
--image-project=debian-cloud \
--machine-type=g1-small \
--scopes cloud-platform \
--metadata-from-file startup-script=startup-script.sh \
--metadata gcs-bucket="$GCS_BUCKET_NAME",redis-host="$REDISHOST",redis-port="$REDISPORT" \
--zone "$ZONE" \
--tags http-server
gcloud compute firewall-rules create allow-http-server-8080 \
--allow tcp:8080 \
--source-ranges 0.0.0.0/0 \
--target-tags http-server \
--description "Allow port 8080 access to http-server"
Java
if [ -z "$GCS_BUCKET_NAME" ]; then
echo "Must set \$GCS_BUCKET_NAME. For example: GCS_BUCKET_NAME=my-bucket"
exit 1
fi
if [ -z "$ZONE" ]; then
ZONE=$(gcloud config get-value compute/zone -q)
echo $ZONE
fi
if [ -z "$WAR" ]; then
WAR=visitcounter-1.0-SNAPSHOT.war
fi
#Build the WAR package
cd ..
mvn clean package
#Copy the WAR artifact to the GCS bucket location
gsutil cp -r target/${WAR} gs://"$GCS_BUCKET_NAME"/gce/
cd gce_deployment
# Create an instance
gcloud compute instances create my-instance \
--image-family=debian-9 \
--image-project=debian-cloud \
--machine-type=g1-small \
--scopes cloud-platform \
--metadata-from-file startup-script=startup-script.sh \
--metadata gcs-bucket=$GCS_BUCKET_NAME,app-war=$WAR \
--zone $ZONE \
--tags http-server
gcloud compute firewall-rules create allow-http-server-8080 \
--allow tcp:8080 \
--source-ranges 0.0.0.0/0 \
--target-tags http-server \
--description "Allow port 8080 access to http-server"
Node.js
관리형 Redis 인스턴스 IP 및 포트를 가리키도록 환경 변수를 설정합니다.
export REDISHOST=XXX.XXX.XXX.XXX
export REDISPORT=6379
if [ -z "$REDISHOST" ]; then
echo "Must set \$REDISHOST. For example: REDISHOST=127.0.0.1"
exit 1
fi
if [ -z "$REDISPORT" ]; then
echo "Must set \$REDISPORT. For example: REDISPORT=6379"
exit 1
fi
if [ -z "$GCS_BUCKET_NAME" ]; then
echo "Must set \$GCS_BUCKET_NAME. For example: GCS_BUCKET_NAME=my-bucket"
exit 1
fi
if [ -z "$ZONE" ]; then
ZONE=$(gcloud config get-value compute/zone -q)
echo $ZONE
fi
#Upload the tar to GCS
tar -cvf app.tar -C .. package.json server.js
gsutil cp app.tar gs://"$GCS_BUCKET_NAME"/gce/
# Create an instance
gcloud compute instances create my-instance \
--image-family=debian-9 \
--image-project=debian-cloud \
--machine-type=g1-small \
--scopes cloud-platform \
--metadata-from-file startup-script=startup-script.sh \
--metadata gcs-bucket=$GCS_BUCKET_NAME,redis-host=$REDISHOST,redis-port=$REDISPORT \
--zone $ZONE \
--tags http-server
gcloud compute firewall-rules create allow-http-server-8080 \
--allow tcp:8080 \
--source-ranges 0.0.0.0/0 \
--target-tags http-server \
--description "Allow port 8080 access to http-server"
Python
관리형 Redis 인스턴스 IP 및 포트를 가리키도록 환경 변수를 설정합니다.
export REDISHOST=XXX.XXX.XXX.XXX
export REDISPORT=6379
if [ -z "$REDISHOST" ]; then
echo "Must set \$REDISHOST. For example: REDISHOST=127.0.0.1"
exit 1
fi
if [ -z "$REDISPORT" ]; then
echo "Must set \$REDISPORT. For example: REDISPORT=6379"
exit 1
fi
if [ -z "$GCS_BUCKET_NAME" ]; then
echo "Must set \$GCS_BUCKET_NAME. For example: GCS_BUCKET_NAME=my-bucket"
exit 1
fi
if [ -z "$ZONE" ]; then
ZONE=$(gcloud config get-value compute/zone -q)
echo $ZONE
fi
#Upload the tar to GCS
tar -cvf app.tar -C .. requirements.txt main.py
# Copy to GCS bucket
gsutil cp app.tar gs://"$GCS_BUCKET_NAME"/gce/
# Create an instance
gcloud compute instances create my-instance \
--image-family=debian-11 \
--image-project=debian-cloud \
--machine-type=g1-small \
--scopes cloud-platform \
--metadata-from-file startup-script=startup-script.sh \
--metadata gcs-bucket=$GCS_BUCKET_NAME,redis-host=$REDISHOST,redis-port=$REDISPORT \
--zone $ZONE \
--tags http-server
gcloud compute firewall-rules create allow-http-server-8080 \
--allow tcp:8080 \
--source-ranges 0.0.0.0/0 \
--target-tags http-server \
--description "Allow port 8080 access to http-server"
GCS_BUCKET_NAME 환경 변수를 설정합니다.
export GCS_BUCKET_NAME=[BUCKET_NAME]/[PATH]
각 항목의 의미는 다음과 같습니다.
BUCKET_NAME
은 Cloud Storage 버킷의 이름입니다.PATH
는 애플리케이션 아티팩트를 저장하려는 디렉터리의 경로(선택사항)입니다.
배포 스크립트를 실행합니다.
chmod +x deploy.sh
./deploy.sh
애플리케이션 시작 스크립트
이 시작 스크립트는 샘플 배포 스크립트에서 인스턴스를 준비하는 데 사용됩니다. 시작 스크립트 및 시작 스크립트 실행 로그 보기에 대한 자세한 내용은 시작 스크립트 실행을 참조하세요.
Go
set -ex
# Talk to the metadata server to get the project id and location of application binary.
PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
export PROJECTID
GCS_BUCKET_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcs-bucket" -H "Metadata-Flavor: Google")
REDISHOST=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-host" -H "Metadata-Flavor: Google")
REDISPORT=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-port" -H "Metadata-Flavor: Google")
# Install dependencies from apt
apt-get update
apt-get install -yq ca-certificates supervisor
# Install logging monitor. The monitor will automatically pickup logs send to
# syslog.
curl "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" --output google-fluentd-install.sh
checksum=$(sha256sum google-fluentd-install.sh | awk '{print $1;}')
if [ "$checksum" != "ec78e9067f45f6653a6749cf922dbc9d79f80027d098c90da02f71532b5cc967" ]; then
echo "Checksum does not match"
exit 1
fi
chmod +x google-fluentd-install.sh && ./google-fluentd-install.sh
service google-fluentd restart &
gsutil cp gs://"$GCS_BUCKET_NAME"/gce/app.tar /app.tar
mkdir -p /app
tar -x -f /app.tar -C /app
chmod +x /app/app
# Create a goapp user. The application will run as this user.
getent passwd goapp || useradd -m -d /home/goapp goapp
chown -R goapp:goapp /app
# Configure supervisor to run the Go app.
cat >/etc/supervisor/conf.d/goapp.conf << EOF
[program:goapp]
directory=/app
environment=HOME="/home/goapp",USER="goapp",REDISHOST=$REDISHOST,REDISPORT=$REDISPORT
command=/app/app
autostart=true
autorestart=true
user=goapp
stdout_logfile=syslog
stderr_logfile=syslog
EOF
supervisorctl reread
supervisorctl update
Java
set -ex
# Talk to the metadata server to get the project id and location of application binary.
PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
GCS_BUCKET_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcs-bucket" -H "Metadata-Flavor: Google")
WAR=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/app-war" -H "Metadata-Flavor: Google")
gsutil cp gs://"$GCS_BUCKET_NAME"/gce/"$WAR" .
# Install dependencies from apt
apt-get update
apt-get install -qq openjdk-8-jdk-headless
# Make Java8 the default
update-alternatives --set java /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java
# Jetty Setup
mkdir -p /opt/jetty/temp
mkdir -p /var/log/jetty
# Get Jetty
curl -L https://repo1.maven.org/maven2/org/eclipse/jetty/jetty-distribution/9.4.10.v20180503/jetty-distribution-9.4.10.v20180503.tar.gz -o jetty9.tgz
tar xf jetty9.tgz --strip-components=1 -C /opt/jetty
# Add a Jetty User
useradd --user-group --shell /bin/false --home-dir /opt/jetty/temp jetty
cd /opt/jetty
# Add running as "jetty"
java -jar /opt/jetty/start.jar --add-to-startd=setuid
cd /
# very important - by renaming the war to root.war, it will run as the root servlet.
mv $WAR /opt/jetty/webapps/root.war
# Make sure "jetty" owns everything.
chown --recursive jetty /opt/jetty
# Configure the default paths for the Jetty service
cp /opt/jetty/bin/jetty.sh /etc/init.d/jetty
echo "JETTY_HOME=/opt/jetty" > /etc/default/jetty
{
echo "JETTY_BASE=/opt/jetty"
echo "TMPDIR=/opt/jetty/temp"
echo "JAVA_OPTIONS=-Djetty.http.port=8080"
echo "JETTY_LOGS=/var/log/jetty"
} >> /etc/default/jetty
# Reload daemon to pick up new service
systemctl daemon-reload
# Install logging monitor. The monitor will automatically pickup logs sent to syslog.
curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash
service google-fluentd restart &
service jetty start
service jetty check
echo "Startup Complete"
Node.js
set -ex
# Talk to the metadata server to get the project id and location of application binary.
PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
GCS_BUCKET_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcs-bucket" -H "Metadata-Flavor: Google")
REDISHOST=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-host" -H "Metadata-Flavor: Google")
REDISPORT=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-port" -H "Metadata-Flavor: Google")
# Install dependencies from apt
apt-get update
# Install Node.js 9
curl -sL https://deb.nodesource.com/setup_9.x | sudo -E bash -
apt-get install -yq ca-certificates supervisor nodejs build-essential
# Install logging monitor. The monitor will automatically pickup logs send to
# syslog.
curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash
service google-fluentd restart &
gsutil cp gs://"$GCS_BUCKET_NAME"/gce/app.tar /app.tar
mkdir -p /app
tar -x -f /app.tar -C /app
cd /app
# Install the app dependencies
npm install
# Create a nodeapp user. The application will run as this user.
getent passwd nodeapp || useradd -m -d /home/nodeapp nodeapp
chown -R nodeapp:nodeapp /app
# Configure supervisor to run the Go app.
cat >/etc/supervisor/conf.d/nodeapp.conf << EOF
[program:nodeapp]
directory=/app
environment=HOME="/home/nodeapp",USER="nodeapp",REDISHOST=$REDISHOST,REDISPORT=$REDISPORT
command=node server.js
autostart=true
autorestart=true
user=nodeapp
stdout_logfile=syslog
stderr_logfile=syslog
EOF
supervisorctl reread
supervisorctl update
Python
set -v
# Talk to the metadata server to get the project id and location of application binary.
PROJECTID=$(curl -s "http://metadata.google.internal/computeMetadata/v1/project/project-id" -H "Metadata-Flavor: Google")
GCS_BUCKET_NAME=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/gcs-bucket" -H "Metadata-Flavor: Google")
REDISHOST=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-host" -H "Metadata-Flavor: Google")
REDISPORT=$(curl -s "http://metadata.google.internal/computeMetadata/v1/instance/attributes/redis-port" -H "Metadata-Flavor: Google")
# Install dependencies from apt
apt-get update
apt-get install -yq \
git build-essential supervisor python python-dev python-pip libffi-dev \
libssl-dev
# Install logging monitor. The monitor will automatically pickup logs send to
# syslog.
curl -s "https://storage.googleapis.com/signals-agents/logging/google-fluentd-install.sh" | bash
service google-fluentd restart &
gsutil cp gs://"$GCS_BUCKET_NAME"/gce/app.tar /app.tar
mkdir -p /app
tar -x -f /app.tar -C /app
cd /app
# Install the app dependencies
pip install --upgrade pip virtualenv
virtualenv /app/env
/app/env/bin/pip install -r /app/requirements.txt
# Create a pythonapp user. The application will run as this user.
getent passwd pythonapp || useradd -m -d /home/pythonapp pythonapp
chown -R pythonapp:pythonapp /app
# Configure supervisor to run the app.
cat >/etc/supervisor/conf.d/pythonapp.conf << EOF
[program:pythonapp]
directory=/app
environment=HOME="/home/pythonapp",USER="pythonapp",REDISHOST=$REDISHOST,REDISPORT=$REDISPORT
command=/app/env/bin/gunicorn main:app --bind 0.0.0:8080
autostart=true
autorestart=true
user=pythonapp
stdout_logfile=syslog
stderr_logfile=syslog
EOF
supervisorctl reread
supervisorctl update
샘플 애플리케이션이 새로 만들어진 Compute Engine 인스턴스에 배포되는 데 몇 분 정도 걸릴 수 있습니다.
인스턴스가 준비되고 시작 스크립트 실행이 완료되었으면 Compute Engine 인스턴스 페이지로 이동하여 외부 IP 주소를 복사합니다.
배포한 샘플 앱을 보려면 http://[EXTERNAL-IP]:8080을 방문하세요.
teardown.sh
스크립트를 사용하여 배포 스크립트로 만든 인스턴스와 방화벽을 삭제할 수 있습니다.
gcloud compute instances delete my-instance
gcloud compute firewall-rules delete allow-http-server-8080