From a9b13d25b33e7f57c871dbde3d31728247ffc40c Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Fri, 4 Oct 2019 22:57:06 -0700 Subject: [PATCH 1/3] Add Cloud Run support to Cloud SQL (MySQL) sample --- cloud-sql/mysql/mysql/Dockerfile | 26 ++++++++++++++++++++++ cloud-sql/mysql/mysql/README.md | 38 +++++++++++++++++++++++++++++++- run/README.md | 2 ++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 cloud-sql/mysql/mysql/Dockerfile diff --git a/cloud-sql/mysql/mysql/Dockerfile b/cloud-sql/mysql/mysql/Dockerfile new file mode 100644 index 0000000000..a278f69ca5 --- /dev/null +++ b/cloud-sql/mysql/mysql/Dockerfile @@ -0,0 +1,26 @@ +# Copyright 2019 Google LLC. All rights reserved. +# Use of this source code is governed by the Apache 2.0 +# license that can be found in the LICENSE file. + +# Use the official lightweight Node.js 10 image. +# https://hub.docker.com/_/node +FROM node:10-slim + +# Create and change to the app directory. +WORKDIR /usr/src/app + +# Copy application dependency manifests to the container image. +# A wildcard is used to ensure both package.json AND package-lock.json are copied. +# Copying this separately prevents re-running npm install on every code change. +COPY package*.json ./ + +# Install dependencies. +# If you add a package-lock.json speed your build by switching to 'npm ci'. +# RUN npm ci --only=production +RUN npm install --production + +# Copy local code to the container image. +COPY . ./ + +# Run the web service on container startup. +CMD [ "npm", "start" ] diff --git a/cloud-sql/mysql/mysql/README.md b/cloud-sql/mysql/mysql/README.md index fb5ccd40b1..2ddef9457b 100644 --- a/cloud-sql/mysql/mysql/README.md +++ b/cloud-sql/mysql/mysql/README.md @@ -32,7 +32,7 @@ secure solution such as [Cloud KMS](https://cloud.google.com/kms/) to help keep ## Running locally To run this application locally, download and install the `cloud_sql_proxy` by -following the instructions [here](https://cloud.google.com/sql/docs/mysql/sql-proxy#install). +[following the instructions](https://cloud.google.com/sql/docs/mysql/sql-proxy#install). Once the proxy is ready, use the following command to start the proxy in the background: @@ -90,3 +90,39 @@ command: gcloud app browse ``` +## Deploy to Cloud Run + +See the [Cloud Run documentation](https://cloud.google.com/run/docs/configuring/connect-cloudsql) +for more details on connecting a Cloud Run service to Cloud SQL. + +1. Build the container image: + +```sh +gcloud builds submit --tag gcr.io/[YOUR_PROJECT_ID]/run-mysql +``` + +2. Deploy the service to Cloud Run: + +```sh +gcloud beta run deploy run-mysql --image gcr.io/[YOUR_PROJECT_ID]/run-mysql +``` + +Take note of the URL output at the end of the deployment process. + +3. Configure the service for use with Cloud Run + +```sh +gcloud beta run services update run-mysql \ + --add-cloudsql-instances [INSTANCE_CONNECTION_NAME] \ + --set-env-vars CLOUD_SQL_CONNECTION_NAME=[INSTANCE_CONNECTION_NAME],\ + DB_USER=[MY_DB_USER],DB_PASS=[MY_DB_PASS],DB_NAME=[MY_DB] +``` +Replace environment variables with the correct values for your Cloud SQL +instance configuration. + +This step can be done as part of deployment but is separated for clarity. + +4. Navigate your browser to the URL noted in step 2. + +For more details about using Cloud Run see http://cloud.run. +Review other [Node.js on Cloud Run samples](../../../run/). diff --git a/run/README.md b/run/README.md index 2cfa44ead3..d99e5022aa 100644 --- a/run/README.md +++ b/run/README.md @@ -13,6 +13,7 @@ |[Pub/Sub][pubsub] | Event-driven service with a Pub/Sub push subscription | [Run on Google Cloud][run_button_pubsub] | |[Image Processing][image_processing] | Event-driven image analysis & transformation | [Run on Google Cloud][run_button_image_processing] | |[Manual Logging][manual_logging] | Structured logging without client library | [Run on Google Cloud][run_button_manual_logging] | +|[Cloud SQL (MySQL)][mysql] | Use MySQL with Cloud Run | - | |[Hello Broken][hello_broken] | Something is wrong, how do you fix it? | [Run on Google Cloud][run_button_hello_broken] | For more Cloud Run samples beyond Node.js, see the main list in the [Cloud Run Samples repository](https://github.com/GoogleCloudPlatform/cloud-run-samples). @@ -116,6 +117,7 @@ for more information. [pubsub]: pubsub/ [image_processing]: image-processing/ [manual_logging]: logging-manual/ +[mysql]: ../cloud-sql/mysql/mysql [hello_broken]: hello-broken/ [run_button_helloworld]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/knative/docs&cloudshell_working_dir=docs/serving/samples/hello-world/helloworld-nodejs [run_button_system_package]: https://console.cloud.google.com/cloudshell/editor?shellonly=true&cloudshell_image=gcr.io/cloudrun/button&cloudshell_git_repo=https://github.com/GoogleCloudPlatform/nodejs-docs-samples&cloudshell_working_dir=run/system-package From 8e370a52557ec5c9a550a7d561015d6b0f3cf12a Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Fri, 4 Oct 2019 22:58:56 -0700 Subject: [PATCH 2/3] Fix promise-mysql compatibility errors --- cloud-sql/mysql/mysql/server.js | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/cloud-sql/mysql/mysql/server.js b/cloud-sql/mysql/mysql/server.js index 774a0f5976..21b56a0199 100644 --- a/cloud-sql/mysql/mysql/server.js +++ b/cloud-sql/mysql/mysql/server.js @@ -43,7 +43,7 @@ const logger = winston.createLogger({ }); // [START cloud_sql_mysql_mysql_create] -const pool = mysql.createPool({ +const connectionOptions = { user: process.env.DB_USER, // e.g. 'my-db-user' password: process.env.DB_PASS, // e.g. 'my-db-password' database: process.env.DB_NAME, // e.g. 'my-database' @@ -83,19 +83,24 @@ const pool = mysql.createPool({ // [END cloud_sql_mysql_mysql_backoff] //[END_EXCLUDE] -}); +}; +let pool; +(async () => { + try { + pool = await mysql.createPool(connectionOptions); + // Wait for tables to be created (if they don't already exist). + await pool.query( + `CREATE TABLE IF NOT EXISTS votes + ( vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, + candidate CHAR(6) NOT NULL, PRIMARY KEY (vote_id) );` + ); + } catch (err) { + logger.err(err); + process.exit(1); + } +})(); // [END cloud_sql_mysql_mysql_create] -// When the server starts, check for tables in the database. -app.on('listening', async () => { - // Wait for tables to be created (if they don't already exist). - await pool.query( - `CREATE TABLE IF NOT EXISTS votes - ( vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, - candidate CHAR(6) NOT NULL, PRIMARY KEY (vote_id) );` - ); -}); - // Serve the index page, showing vote tallies. app.get('/', async (req, res) => { // Get the 5 most recent votes. From 8d0cd66acf82853235b9425945fe12a486f54b9e Mon Sep 17 00:00:00 2001 From: Adam Ross Date: Tue, 8 Oct 2019 11:38:19 -0700 Subject: [PATCH 3/3] named functions for top-level async ops --- cloud-sql/mysql/mysql/server.js | 109 ++++++++++++++++---------------- 1 file changed, 54 insertions(+), 55 deletions(-) diff --git a/cloud-sql/mysql/mysql/server.js b/cloud-sql/mysql/mysql/server.js index 21b56a0199..69e6e55fe8 100644 --- a/cloud-sql/mysql/mysql/server.js +++ b/cloud-sql/mysql/mysql/server.js @@ -43,64 +43,63 @@ const logger = winston.createLogger({ }); // [START cloud_sql_mysql_mysql_create] -const connectionOptions = { - user: process.env.DB_USER, // e.g. 'my-db-user' - password: process.env.DB_PASS, // e.g. 'my-db-password' - database: process.env.DB_NAME, // e.g. 'my-database' - // If connecting via unix domain socket, specify the path - socketPath: `/cloudsql/${process.env.CLOUD_SQL_CONNECTION_NAME}`, - // If connecting via TCP, enter the IP and port instead - // host: 'localhost', - // port: 3306, - - //[START_EXCLUDE] - - // [START cloud_sql_mysql_mysql_limit] - // 'connectionLimit' is the maximum number of connections the pool is allowed - // to keep at once. - connectionLimit: 5, - // [END cloud_sql_mysql_mysql_limit] - - // [START cloud_sql_mysql_mysql_timeout] - // 'connectTimeout' is the maximum number of milliseconds before a timeout - // occurs during the initial connection to the database. - connectTimeout: 10000, // 10 seconds - // 'acquireTimeout' is the maximum number of milliseconds to wait when - // checking out a connection from the pool before a timeout error occurs. - acquireTimeout: 10000, // 10 seconds - // 'waitForConnections' determines the pool's action when no connections are - // free. If true, the request will queued and a connection will be presented - // when ready. If false, the pool will call back with an error. - waitForConnections: true, // Default: true - // 'queueLimit' is the maximum number of requests for connections the pool - // will queue at once before returning an error. If 0, there is no limit. - queueLimit: 0, // Default: 0 - // [END cloud_sql_mysql_mysql_timeout] - - // [START cloud_sql_mysql_mysql_backoff] - // The mysql module automatically uses exponential delays between failed - // connection attempts. - // [END cloud_sql_mysql_mysql_backoff] - - //[END_EXCLUDE] -}; let pool; -(async () => { - try { - pool = await mysql.createPool(connectionOptions); - // Wait for tables to be created (if they don't already exist). - await pool.query( - `CREATE TABLE IF NOT EXISTS votes - ( vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, - candidate CHAR(6) NOT NULL, PRIMARY KEY (vote_id) );` - ); - } catch (err) { - logger.err(err); - process.exit(1); - } -})(); +const createPool = async () => { + pool = await mysql.createPool({ + user: process.env.DB_USER, // e.g. 'my-db-user' + password: process.env.DB_PASS, // e.g. 'my-db-password' + database: process.env.DB_NAME, // e.g. 'my-database' + // If connecting via unix domain socket, specify the path + socketPath: `/cloudsql/${process.env.CLOUD_SQL_CONNECTION_NAME}`, + // If connecting via TCP, enter the IP and port instead + // host: 'localhost', + // port: 3306, + + //[START_EXCLUDE] + + // [START cloud_sql_mysql_mysql_limit] + // 'connectionLimit' is the maximum number of connections the pool is allowed + // to keep at once. + connectionLimit: 5, + // [END cloud_sql_mysql_mysql_limit] + + // [START cloud_sql_mysql_mysql_timeout] + // 'connectTimeout' is the maximum number of milliseconds before a timeout + // occurs during the initial connection to the database. + connectTimeout: 10000, // 10 seconds + // 'acquireTimeout' is the maximum number of milliseconds to wait when + // checking out a connection from the pool before a timeout error occurs. + acquireTimeout: 10000, // 10 seconds + // 'waitForConnections' determines the pool's action when no connections are + // free. If true, the request will queued and a connection will be presented + // when ready. If false, the pool will call back with an error. + waitForConnections: true, // Default: true + // 'queueLimit' is the maximum number of requests for connections the pool + // will queue at once before returning an error. If 0, there is no limit. + queueLimit: 0, // Default: 0 + // [END cloud_sql_mysql_mysql_timeout] + + // [START cloud_sql_mysql_mysql_backoff] + // The mysql module automatically uses exponential delays between failed + // connection attempts. + // [END cloud_sql_mysql_mysql_backoff] + + //[END_EXCLUDE] + }); +}; +createPool(); // [END cloud_sql_mysql_mysql_create] +const ensureSchema = async () => { + // Wait for tables to be created (if they don't already exist). + await pool.query( + `CREATE TABLE IF NOT EXISTS votes + ( vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, + candidate CHAR(6) NOT NULL, PRIMARY KEY (vote_id) );` + ); +}; +ensureSchema(); + // Serve the index page, showing vote tallies. app.get('/', async (req, res) => { // Get the 5 most recent votes.