Skip to content

Commit 4492609

Browse files
authored
feat: Update Postgres samples to include more connection methods (#7080)
1 parent 4039e1e commit 4492609

File tree

12 files changed

+645
-164
lines changed

12 files changed

+645
-164
lines changed

cloud-sql/postgres/servlet/.env.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
INSTANCE_CONNECTION_NAME: <PROJECT-ID>:<INSTANCE-REGION>:INSTANCE-NAME>
2+
INSTANCE_UNIX_SOCKET: /cloudsql/<PROJECT-ID>:<INSTANCE-REGION>:INSTANCE-NAME>
3+
INSTANCE_HOST: '127.0.0.1'
4+
DB_PORT: 5432
5+
DB_USER: <YOUR_DB_USER_NAME>
6+
DB_IAM_USER: <YOUR_DB_IAM_USER_NAME>
7+
DB_PASS: <YOUR_DB_PASSWORD>
8+
DB_NAME: <YOUR_DB_NAME>

cloud-sql/postgres/servlet/README.md

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,50 @@ export DB_PASS='my-db-pass'
2727
export DB_NAME='my_db'
2828
```
2929
Note: Saving credentials in environment variables is convenient, but not secure - consider a more
30-
secure solution such as [Cloud KMS](https://cloud.google.com/kms/) to help keep secrets safe.
30+
secure solution such as [Secret Manager](https://cloud.google.com/secret-manager/) to help keep secrets safe.
31+
32+
## Configure SSL Certificates
33+
For deployments that connect directly to a Cloud SQL instance with TCP,
34+
without using the Cloud SQL Proxy,
35+
configuring SSL certificates will ensure the connection is encrypted.
36+
1. Use the gcloud CLI to [download the server certificate](https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#server-certs) for your Cloud SQL instance.
37+
- Get information about the service certificate:
38+
```
39+
gcloud beta sql ssl server-ca-certs list --instance=INSTANCE_NAME
40+
```
41+
- Create a server certificate:
42+
```
43+
gcloud beta sql ssl server-ca-certs create --instance=INSTANCE_NAME
44+
```
45+
- Download the certificate information to a local PEM file
46+
```
47+
gcloud beta sql ssl server-ca-certs list \
48+
--format="value(cert)" \
49+
--instance=INSTANCE_NAME > \
50+
server-ca.pem
51+
```
52+
53+
2. Use the gcloud CLI to [create and download a client public key certificate and client private key](https://cloud.google.com/sql/docs/postgres/configure-ssl-instance#client-certs)
54+
- Create a client certificate using the ssl client-certs create command:
55+
```
56+
gcloud sql ssl client-certs create CERT_NAME client-key.pem --instance=INSTANCE_NAME
57+
```
58+
- Retrieve the public key for the certificate you just created and copy it into the client-cert.pem file with the ssl client-certs describe command:
59+
```
60+
gcloud sql ssl client-certs describe CERT_NAME \
61+
--instance=INSTANCE_NAME \
62+
--format="value(cert)" > client-cert.pem
63+
```
64+
3. Convert the downloaded PEM certificate and key to a PKCS12 archive using `openssl`:
65+
```
66+
openssl pkcs12 -export -in client-cert.pem -inkey client-key.pem \
67+
-name "mysqlclient" -passout pass:<password> -out client-keystore.p12
68+
```
69+
4. Set the `SSL_CLIENT_KEY_PATH` and `SSL_CLIENT_KEY_PASSWD` environment variables to the values from the previous step.
70+
The client key path should point to the PKCS12 archive file.
71+
6. Set the `SSL_SERVER_CA_PATH` environment variables to point to the `server-ca.pem` file downloaded earlier
3172
3273
## Deploying locally
33-
3474
To run this application locally, run the following command inside the project folder:
3575
3676
```bash
@@ -48,13 +88,19 @@ and verify that
4888
has been added in your build section as a plugin.
4989

5090

51-
### Development Server
91+
### App Engine Development Server
5292

5393
The following command will run the application locally in the the GAE-development server:
5494
```bash
5595
mvn appengine:run
5696
```
5797

98+
### Cloud Functions Development Server
99+
To run the application locally as a Cloud Function, run the following command:
100+
```
101+
mvn function:run -Drun.functionTarget=com.example.cloudsql.functions.Main
102+
```
103+
58104
### Deploy to Google App Engine
59105

60106
First, update `src/main/webapp/WEB-INF/appengine-web.xml` with the correct values to pass the
@@ -120,3 +166,14 @@ mvn clean package com.google.cloud.tools:jib-maven-plugin:2.8.0:build \
120166

121167
For more details about using Cloud Run see http://cloud.run.
122168
Review other [Java on Cloud Run samples](../../../run/).
169+
170+
### Deploy to Google Cloud Functions
171+
172+
To deploy the application to Cloud Functions, first fill in the values for required environment variables in `.env.yaml`. Then run the following command
173+
```
174+
gcloud functions deploy mysql-sample \
175+
--trigger-http \
176+
--entry-point com.example.cloudsql.functions.Main \
177+
--runtime java11 \
178+
--env-vars-file .env.yaml
179+
```

cloud-sql/postgres/servlet/pom.xml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,27 @@
8282
<version>1.1.3</version>
8383
<scope>test</scope>
8484
</dependency>
85+
<!-- Only required for Cloud Functions Deployments -->
86+
<dependency>
87+
<groupId>com.google.cloud.functions.invoker</groupId>
88+
<artifactId>java-function-invoker</artifactId>
89+
<version>1.0.1</version>
90+
</dependency>
91+
<dependency>
92+
<groupId>com.google.cloud.functions</groupId>
93+
<artifactId>functions-framework-api</artifactId>
94+
<version>1.0.4</version>
95+
<scope>provided</scope>
96+
</dependency>
8597
</dependencies>
8698

8799
<build>
88100
<plugins>
101+
<plugin>
102+
<groupId>org.apache.maven.plugins</groupId>
103+
<artifactId>maven-war-plugin</artifactId>
104+
<version>3.3.2</version>
105+
</plugin>
89106
<plugin>
90107
<groupId>org.eclipse.jetty</groupId>
91108
<artifactId>jetty-maven-plugin</artifactId>
@@ -103,7 +120,22 @@
103120
<deploy.projectId>GCLOUD_CONFIG</deploy.projectId>
104121
<deploy.version>GCLOUD_CONFIG</deploy.version>
105122
</configuration>
106-
</plugin>
123+
</plugin>
124+
<plugin>
125+
<!--
126+
Google Cloud Functions Framework Maven plugin
127+
This plugin allows you to run Cloud Functions Java code
128+
locally. Use the following terminal command to run a
129+
given function locally:s
130+
mvn function:run -Drun.functionTarget=your.package.yourFunction
131+
-->
132+
<groupId>com.google.cloud.functions</groupId>
133+
<artifactId>function-maven-plugin</artifactId>
134+
<version>0.10.0</version>
135+
<configuration>
136+
<functionTarget>com.example.cloudsql.functions.Main</functionTarget>
137+
</configuration>
138+
</plugin>
107139
</plugins>
108140
</build>
109141
</project>

cloud-sql/postgres/servlet/src/main/java/com/example/cloudsql/ConnectionPoolContextListener.java

Lines changed: 6 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,8 @@
1616

1717
package com.example.cloudsql;
1818

19-
import com.zaxxer.hikari.HikariConfig;
2019
import com.zaxxer.hikari.HikariDataSource;
2120
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
22-
import java.sql.Connection;
23-
import java.sql.PreparedStatement;
2421
import java.sql.SQLException;
2522
import javax.servlet.ServletContext;
2623
import javax.servlet.ServletContextEvent;
@@ -34,102 +31,6 @@
3431
@WebListener("Creates a connection pool that is stored in the Servlet's context for later use.")
3532
public class ConnectionPoolContextListener implements ServletContextListener {
3633

37-
// Saving credentials in environment variables is convenient, but not secure - consider a more
38-
// secure solution such as https://cloud.google.com/kms/ to help keep secrets safe.
39-
private static final String INSTANCE_CONNECTION_NAME =
40-
System.getenv("INSTANCE_CONNECTION_NAME");
41-
private static final String DB_USER = System.getenv("DB_USER");
42-
private static final String DB_PASS = System.getenv("DB_PASS");
43-
private static final String DB_NAME = System.getenv("DB_NAME");
44-
45-
@SuppressFBWarnings(
46-
value = "USBR_UNNECESSARY_STORE_BEFORE_RETURN",
47-
justification = "Necessary for sample region tag.")
48-
private DataSource createConnectionPool() {
49-
// [START cloud_sql_postgres_servlet_create]
50-
// Note: For Java users, the Cloud SQL JDBC Socket Factory can provide authenticated connections
51-
// which is preferred to using the Cloud SQL Auth Proxy with Unix sockets.
52-
// See https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory for details.
53-
54-
// The configuration object specifies behaviors for the connection pool.
55-
HikariConfig config = new HikariConfig();
56-
57-
// The following URL is equivalent to setting the config options below:
58-
// jdbc:postgresql:///<DB_NAME>?cloudSqlInstance=<INSTANCE_CONNECTION_NAME>&
59-
// socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=<DB_USER>&password=<DB_PASS>
60-
// See the link below for more info on building a JDBC URL for the Cloud SQL JDBC Socket Factory
61-
// https://github.com/GoogleCloudPlatform/cloud-sql-jdbc-socket-factory#creating-the-jdbc-url
62-
63-
// Configure which instance and what database user to connect with.
64-
config.setJdbcUrl(String.format("jdbc:postgresql:///%s", DB_NAME));
65-
config.setUsername(DB_USER); // e.g. "root", "postgres"
66-
config.setPassword(DB_PASS); // e.g. "my-password"
67-
68-
config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory");
69-
config.addDataSourceProperty("cloudSqlInstance", INSTANCE_CONNECTION_NAME);
70-
71-
72-
// The ipTypes argument can be used to specify a comma delimited list of preferred IP types
73-
// for connecting to a Cloud SQL instance. The argument ipTypes=PRIVATE will force the
74-
// SocketFactory to connect with an instance's associated private IP.
75-
config.addDataSourceProperty("ipTypes", "PUBLIC,PRIVATE");
76-
77-
// ... Specify additional connection properties here.
78-
// [START_EXCLUDE]
79-
80-
// [START cloud_sql_postgres_servlet_limit]
81-
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
82-
// values for this setting are highly variable on app design, infrastructure, and database.
83-
config.setMaximumPoolSize(5);
84-
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
85-
// Additional connections will be established to meet this value unless the pool is full.
86-
config.setMinimumIdle(5);
87-
// [END cloud_sql_postgres_servlet_limit]
88-
89-
// [START cloud_sql_postgres_servlet_timeout]
90-
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
91-
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
92-
// SQLException.
93-
config.setConnectionTimeout(10000); // 10 seconds
94-
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
95-
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
96-
config.setIdleTimeout(600000); // 10 minutes
97-
// [END cloud_sql_postgres_servlet_timeout]
98-
99-
// [START cloud_sql_postgres_servlet_backoff]
100-
// Hikari automatically delays between failed connection attempts, eventually reaching a
101-
// maximum delay of `connectionTimeout / 2` between attempts.
102-
// [END cloud_sql_postgres_servlet_backoff]
103-
104-
// [START cloud_sql_postgres_servlet_lifetime]
105-
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
106-
// live longer than this many milliseconds will be closed and reestablished between uses. This
107-
// value should be several minutes shorter than the database's timeout value to avoid unexpected
108-
// terminations.
109-
config.setMaxLifetime(1800000); // 30 minutes
110-
// [END cloud_sql_postgres_servlet_lifetime]
111-
112-
// [END_EXCLUDE]
113-
114-
// Initialize the connection pool using the configuration object.
115-
DataSource pool = new HikariDataSource(config);
116-
// [END cloud_sql_postgres_servlet_create]
117-
return pool;
118-
}
119-
120-
private void createTable(DataSource pool) throws SQLException {
121-
// Safely attempt to create the table schema.
122-
try (Connection conn = pool.getConnection()) {
123-
String stmt =
124-
"CREATE TABLE IF NOT EXISTS votes ( "
125-
+ "vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, candidate CHAR(6) NOT NULL,"
126-
+ " PRIMARY KEY (vote_id) );";
127-
try (PreparedStatement createTableStatement = conn.prepareStatement(stmt);) {
128-
createTableStatement.execute();
129-
}
130-
}
131-
}
132-
13334
@Override
13435
public void contextDestroyed(ServletContextEvent event) {
13536
// This function is called when the Servlet is destroyed.
@@ -146,11 +47,15 @@ public void contextInitialized(ServletContextEvent event) {
14647
ServletContext servletContext = event.getServletContext();
14748
DataSource pool = (DataSource) servletContext.getAttribute("my-pool");
14849
if (pool == null) {
149-
pool = createConnectionPool();
50+
if (System.getenv("INSTANCE_HOST") != null) {
51+
pool = TcpConnectionPoolFactory.createConnectionPool();
52+
} else {
53+
pool = ConnectorConnectionPoolFactory.createConnectionPool();
54+
}
15055
servletContext.setAttribute("my-pool", pool);
15156
}
15257
try {
153-
createTable(pool);
58+
Utils.createTable(pool);
15459
} catch (SQLException ex) {
15560
throw new RuntimeException(
15661
"Unable to verify table schema. Please double check the steps"
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.example.cloudsql;
18+
19+
import com.zaxxer.hikari.HikariConfig;
20+
21+
public class ConnectionPoolFactory {
22+
23+
public static HikariConfig configureConnectionPool(HikariConfig config) {
24+
// [START cloud_sql_postgres_servlet_limit]
25+
// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
26+
// values for this setting are highly variable on app design, infrastructure, and database.
27+
config.setMaximumPoolSize(5);
28+
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
29+
// Additional connections will be established to meet this value unless the pool is full.
30+
config.setMinimumIdle(5);
31+
// [END cloud_sql_postgres_servlet_limit]
32+
33+
// [START cloud_sql_postgres_servlet_timeout]
34+
// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
35+
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
36+
// SQLException.
37+
config.setConnectionTimeout(10000); // 10 seconds
38+
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
39+
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
40+
config.setIdleTimeout(600000); // 10 minutes
41+
// [END cloud_sql_postgres_servlet_timeout]
42+
43+
// [START cloud_sql_postgres_servlet_backoff]
44+
// Hikari automatically delays between failed connection attempts, eventually reaching a
45+
// maximum delay of `connectionTimeout / 2` between attempts.
46+
// [END cloud_sql_postgres_servlet_backoff]
47+
48+
// [START cloud_sql_postgres_servlet_lifetime]
49+
// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
50+
// live longer than this many milliseconds will be closed and reestablished between uses. This
51+
// value should be several minutes shorter than the database's timeout value to avoid unexpected
52+
// terminations.
53+
config.setMaxLifetime(1800000); // 30 minutes
54+
// [END cloud_sql_postgres_servlet_lifetime]
55+
return config;
56+
}
57+
}

0 commit comments

Comments
 (0)