Skip to content

Memorystore: Add Dockerfile for Cloud Run deployment #4191

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from Aug 5, 2020
2 changes: 1 addition & 1 deletion .kokoro/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ RUN rm /tmp/get-pip.py
RUN pip install --no-cache-dir virtualenv

# Setup Cloud SDK
ENV CLOUD_SDK_VERSION 293.0.0
ENV CLOUD_SDK_VERSION 304.0.0
# Use system python for cloud sdk.
ENV CLOUDSDK_PYTHON python3.6
RUN wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-sdk-$CLOUD_SDK_VERSION-linux-x86_64.tar.gz
Expand Down
7 changes: 7 additions & 0 deletions memorystore/redis/.gcloudignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
cloud_run_deployment/
gae_flex_deployment/
gae_standard_deployment/
gae_standard_py2_deployment/
gce_deployment/
gke_deployment/
README.md
17 changes: 17 additions & 0 deletions memorystore/redis/cloud_run_deployment/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.7-slim

# Copy local code to the container image.
ENV APP_HOME /app
WORKDIR $APP_HOME
COPY . ./

# Install production dependencies.
RUN pip install -r requirements.txt

# Run the web service on container startup. Here we use the gunicorn
# webserver, with one worker process and 8 threads.
# For environments with multiple CPU cores, increase the number of workers
# to be equal to the cores available.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app
263 changes: 263 additions & 0 deletions memorystore/redis/cloud_run_deployment/e2e_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
# Copyright 2020 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.

# This test creates a Memorystore instance, Serverless VPC Access
# connector, and Cloud Run service and tests that the Cloud Run
# service can reach the Memorystore instance.

import os
import re
import subprocess
from urllib import request
import uuid

import pytest


@pytest.fixture()
def services():
# Unique suffix to create distinct service names
suffix = uuid.uuid4().hex[:10]
project = os.environ["GOOGLE_CLOUD_PROJECT"]

# Create a VPC network
network_name = f"test-network-{suffix}"
subprocess.run(
[
"gcloud",
"compute",
"networks",
"create",
network_name,
"--project",
project,
], check=True
)

# Create a Serverless VPC Access connector
connector_name = f"test-connector-{suffix}"
subprocess.run(
[
"gcloud",
"compute",
"networks",
"vpc-access",
"connectors",
"create",
connector_name,
"--network",
network_name,
"--region=us-central1",
"--range=192.168.16.0/28",
"--project",
project,
], check=True
)

# Create a Memorystore Redis instance
instance_name = f"test-instance-{suffix}"
subprocess.run(
[
"gcloud",
"redis",
"instances",
"create",
instance_name,
"--region=us-central1",
"--network",
network_name,
"--project",
project,
], check=True
)

# Get the Redis instance's IP
redis_host = subprocess.run(
[
"gcloud",
"redis",
"instances",
"describe",
instance_name,
"--region=us-central1",
"--format=value(host)",
"--project",
project,
],
stdout=subprocess.PIPE,
check=True
).stdout.strip().decode()

# Build container image for Cloud Run deployment
image_name = f"gcr.io/{project}/test-visit-count-{suffix}"
subprocess.run(
[
"cp",
"cloud_run_deployment/Dockerfile",
".",
], check=True
)
subprocess.run(
[
"gcloud",
"builds",
"submit",
"--tag",
image_name,
"--project",
project,
], check=True
)
subprocess.run(["rm", "Dockerfile"], check=True)

# Deploy image to Cloud Run
service_name = f"test-visit-count-{suffix}"
subprocess.run(
[
"gcloud",
"run",
"deploy",
service_name,
"--image",
image_name,
"--platform=managed",
"--no-allow-unauthenticated",
"--region=us-central1",
"--vpc-connector",
connector_name,
"--set-env-vars",
f"REDISHOST={redis_host},REDISPORT=6379",
"--project",
project,
], check=True
)

# Get Cloud Run service URL and auth token
service_url = subprocess.run(
[
"gcloud",
"run",
"services",
"describe",
service_name,
"--platform=managed",
"--region=us-central1",
"--format=value(status.url)",
"--project",
project,
],
stdout=subprocess.PIPE,
check=True
).stdout.strip().decode()
auth_token = subprocess.run(
["gcloud", "auth", "print-identity-token"],
stdout=subprocess.PIPE,
check=True
).stdout.strip().decode()

yield service_url, auth_token

# Delete Cloud Run service
subprocess.run(
[
"gcloud",
"run",
"services",
"delete",
service_name,
"--platform=managed",
"--region=us-central1",
"--quiet",
"--project",
project,
], check=True
)

# Delete container image
subprocess.run(
[
"gcloud",
"container",
"images",
"delete",
image_name,
"--quiet",
"--project",
project,
], check=True
)

# Delete Redis instance
subprocess.run(
[
"gcloud",
"redis",
"instances",
"delete",
instance_name,
"--region=us-central1",
"--quiet",
"--async",
"--project",
project,
], check=True
)

# Delete Serverless VPC Access connector
subprocess.run(
[
"gcloud",
"compute",
"networks",
"vpc-access",
"connectors",
"delete",
connector_name,
"--region=us-central1",
"--quiet",
"--project",
project,
], check=True
)

# Delete VPC network
subprocess.run(
[
"gcloud",
"compute",
"networks",
"delete",
network_name,
"--quiet",
"--project",
project,
], check=True
)


def test_end_to_end(services):
service_url, auth_token = services

req = request.Request(
service_url,
headers={
"Authorization": f"Bearer {auth_token}"
}
)

response = request.urlopen(req)
assert response.status == 200

body = response.read().decode()
assert re.search(r"Visitor number: \d+", body) is not None
3 changes: 2 additions & 1 deletion memorystore/redis/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def server_error(e):

if __name__ == '__main__':
# This is used when running locally. Gunicorn is used to run the
# application on Google App Engine. See entrypoint in app.yaml.
# 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)
# [END memorystore_main_py]
39 changes: 39 additions & 0 deletions memorystore/redis/noxfile_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Copyright 2020 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.

# Default TEST_CONFIG_OVERRIDE for python repos.

# You can copy this file into your directory, then it will be imported from
# the noxfile.py.

# The source of truth:
# https://github.com/GoogleCloudPlatform/python-docs-samples/blob/master/noxfile_config.py

TEST_CONFIG_OVERRIDE = {
# You can opt out from the test for specific Python versions.

# We only run the cloud run tests in py38 session.
'ignored_versions': ["2.7", "3.6", "3.7"],

# An envvar key for determining the project id to use. Change it
# to 'BUILD_SPECIFIC_GCLOUD_PROJECT' if you want to opt in using a
# build specific Cloud project. You can also use your own string
# to use your own Cloud project.
'gcloud_project_env': 'GOOGLE_CLOUD_PROJECT',
# 'gcloud_project_env': 'BUILD_SPECIFIC_GCLOUD_PROJECT',

# A dictionary you want to inject into your test. Don't put any
# secrets here. These values will override predefined values.
'envs': {},
}