-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: Cloud Run sidecar with Java app sample (#959)
* docs: Cloud Run sidecar with Java app sample Adds a sample for running PGAdapter as a sidecar on Cloud Run together with an application written in Java. The main application connects to PGAdapter using the PostgreSQL JDBC driver and uses Unix domain sockets for the underlying connection. The Unix domain sockets uses an in-memory volume. * chore: update copyright year * chore: update sample to use environment variables Modified the sample slightly to use the same setup as the Go sample. This means using environment variables for the Cloud Spanner database that should be used, and starting PGAdapter without a default project, instance and database. This makes the PGAdapter instance usable for connections to different databases, which means that applications can connect to multiple databases using a single PGAdapter instance. It also shows how a single setup can easily be used for both test and production. * fix: add service replace step * chore: remove duplicated line
- Loading branch information
Showing
5 changed files
with
382 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
# PGAdapter Cloud Run Sidecar Sample for Java | ||
|
||
This sample application shows how to build and deploy a Java application with PGAdapter as a sidecar | ||
to Google Cloud Run. The Java application connects to PGAdapter using a Unix domain socket using an | ||
in-memory volume. This gives the lowest possible latency between your application and PGAdapter. | ||
|
||
The sample is based on the [Cloud Run Quickstart Guide for Java](https://cloud.google.com/run/docs/quickstarts/build-and-deploy/deploy-java-service). | ||
Refer to that guide for more in-depth information on how to work with Cloud Run. | ||
|
||
## Configure | ||
|
||
Modify the `service.yaml` file to match your Cloud Run project and region, and your Cloud Spanner database: | ||
|
||
```shell | ||
# TODO: Modify MY-REGION and MY-PROJECT to match your application container image. | ||
image: MY-REGION.pkg.dev/MY-PROJECT/cloud-run-source-deploy/pgadapter-sidecar-example | ||
... | ||
# TODO: Modify these environment variables to match your Cloud Spanner database. | ||
env: | ||
- name: SPANNER_PROJECT | ||
value: my-project | ||
- name: SPANNER_INSTANCE | ||
value: my-instance | ||
- name: SPANNER_DATABASE | ||
value: my-database | ||
``` | ||
|
||
## Optional - Build and Run Locally | ||
|
||
You can test the application locally to verify that the Cloud Spanner project, instance, and database | ||
configuration is correct. For this, you first need to start PGAdapter on your local machine and then | ||
run the application. | ||
|
||
```shell | ||
docker pull gcr.io/cloud-spanner-pg-adapter/pgadapter | ||
docker run \ | ||
--name pgadapter-cloud-run-example \ | ||
--rm -d -p 5432:5432 \ | ||
-v /path/to/credentials.json:/credentials.json:ro \ | ||
gcr.io/cloud-spanner-pg-adapter/pgadapter \ | ||
-c /credentials.json -x | ||
export SPANNER_PROJECT=my-project | ||
export SPANNER_INSTANCE=my-instance | ||
export SPANNER_DATABASE=my-database | ||
mvn spring-boot:run | ||
``` | ||
|
||
This will start a web server on port 8080. Run the following command to verify that it works: | ||
|
||
```shell | ||
curl localhost:8080 | ||
``` | ||
|
||
Stop the PGAdapter Docker container again with: | ||
|
||
```shell | ||
docker container stop pgadapter-cloud-run-example | ||
``` | ||
|
||
## Deploying to Cloud Run | ||
|
||
First make sure that you have authentication set up for pushing Docker images. | ||
|
||
```shell | ||
gcloud auth configure-docker | ||
``` | ||
|
||
Build the application from source and deploy it to Cloud Run. Replace the generated service | ||
file with the one from this directory. The latter will add PGAdapter as a sidecar container to the | ||
service. | ||
|
||
```shell | ||
gcloud run deploy pgadapter-sidecar-example --source . | ||
gcloud run services replace service.yaml | ||
``` | ||
|
||
__NOTE__: This example does not specify any credentials for PGAdapter when it is run on Cloud Run. This means that | ||
PGAdapter will use the default credentials that is used by Cloud Run. This is by default the default compute engine | ||
service account. See https://cloud.google.com/run/docs/securing/service-identity for more information on how service | ||
accounts work on Google Cloud Run. | ||
|
||
Test the service (replace URL with your actual service URL): | ||
|
||
```shell | ||
curl https://my-service-xyz.run.app | ||
``` | ||
|
||
### Authenticated Cloud Run Service | ||
|
||
If your Cloud Run service requires authentication, then first add an IAM binding for your own account and include | ||
an authentication header with the request: | ||
|
||
```shell | ||
gcloud run services add-iam-policy-binding my-service \ | ||
--member='user:your-email@gmail.com' \ | ||
--role='roles/run.invoker' | ||
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://my-service-xyz.run.app | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
<!-- | ||
Copyright 2023 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. | ||
--> | ||
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
<properties> | ||
<java.version>1.8</java.version> | ||
<maven.compiler.source>1.8</maven.compiler.source> | ||
<maven.compiler.target>1.8</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
|
||
<spring-boot.version>2.7.11</spring-boot.version> | ||
<junixsocket.version>2.6.2</junixsocket.version> | ||
</properties> | ||
|
||
<modelVersion>4.0.0</modelVersion> | ||
<groupId>com.google.cloud</groupId> | ||
<artifactId>google-cloud-spanner-pgadapter-samples-cloud-run-sidecar</artifactId> | ||
<version>0.1.0-SNAPSHOT</version> | ||
<name>Google Cloud Spanner PGAdapter Cloud Run Sidecar Sample for Java</name> | ||
<packaging>jar</packaging> | ||
<description> | ||
Sample for deploying a Java application with PGAdapter as a sidecar to Cloud Run | ||
</description> | ||
|
||
<dependencyManagement> | ||
<dependencies> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-dependencies</artifactId> | ||
<version>${spring-boot.version}</version> | ||
<type>pom</type> | ||
<scope>import</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>org.postgresql</groupId> | ||
<artifactId>postgresql</artifactId> | ||
<version>42.6.0</version> | ||
<scope>compile</scope> | ||
</dependency> | ||
<!-- This dependency allows us to use Unix Domain Sockets to connect to PGAdapter/PostgreSQL. --> | ||
<dependency> | ||
<groupId>com.kohlschutter.junixsocket</groupId> | ||
<artifactId>junixsocket-core</artifactId> | ||
<version>${junixsocket.version}</version> | ||
<type>pom</type> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.kohlschutter.junixsocket</groupId> | ||
<artifactId>junixsocket-common</artifactId> | ||
<version>${junixsocket.version}</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-web</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-autoconfigure</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-configuration-processor</artifactId> | ||
<optional>true</optional> | ||
</dependency> | ||
</dependencies> | ||
<build> | ||
<plugins> | ||
<plugin> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-maven-plugin</artifactId> | ||
<version>${spring-boot.version}</version> | ||
<executions> | ||
<execution> | ||
<goals> | ||
<goal>repackage</goal> | ||
</goals> | ||
</execution> | ||
</executions> | ||
</plugin> | ||
<plugin> | ||
<groupId>com.google.cloud.tools</groupId> | ||
<artifactId>jib-maven-plugin</artifactId> | ||
<version>3.3.2</version> | ||
<configuration> | ||
<to> | ||
<image>gcr.io/PROJECT_ID/pgadapter-sidecar-example</image> | ||
</to> | ||
</configuration> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
apiVersion: serving.knative.dev/v1 | ||
kind: Service | ||
metadata: | ||
annotations: | ||
run.googleapis.com/launch-stage: BETA | ||
name: pgadapter-sidecar-example | ||
spec: | ||
template: | ||
metadata: | ||
annotations: | ||
run.googleapis.com/execution-environment: gen1 | ||
# This registers 'pgadapter' as a dependency of 'app' and will ensure that pgadapter starts | ||
# before the app container. | ||
run.googleapis.com/container-dependencies: '{"app":["pgadapter"]}' | ||
spec: | ||
# Create an in-memory volume that can be used for Unix domain sockets. | ||
volumes: | ||
- name: sockets-dir | ||
emptyDir: | ||
sizeLimit: 50Mi | ||
medium: Memory | ||
containers: | ||
# This is the main application container. | ||
- name: app | ||
# TODO: Modify MY-REGION and MY-PROJECT to match your application container image. | ||
# Example: europe-north1-docker.pkg.dev/my-test-project/cloud-run-source-deploy/pgadapter-sidecar-example | ||
image: MY-REGION.pkg.dev/MY-PROJECT/cloud-run-source-deploy/pgadapter-sidecar-example | ||
# TODO: Modify these environment variables to match your Cloud Spanner database. | ||
# The PGADAPTER_HOST variable is set to point to /sockets, which is the shared in-memory | ||
# volume that is used for Unix domain sockets. | ||
env: | ||
- name: SPANNER_PROJECT | ||
value: my-project | ||
- name: SPANNER_INSTANCE | ||
value: my-instance | ||
- name: SPANNER_DATABASE | ||
value: my-database | ||
- name: PGADAPTER_HOST | ||
value: /sockets | ||
- name: PGADAPTER_PORT | ||
value: "5432" | ||
ports: | ||
- containerPort: 8080 | ||
volumeMounts: | ||
- mountPath: /sockets | ||
name: sockets-dir | ||
# This is the PGAdapter sidecar container. | ||
- name: pgadapter | ||
image: gcr.io/cloud-spanner-pg-adapter/pgadapter | ||
volumeMounts: | ||
- mountPath: /sockets | ||
name: sockets-dir | ||
args: | ||
- -dir /sockets | ||
- -x | ||
# Add a startup probe that checks that PGAdapter is listening on port 5432. | ||
# NOTE: This probe will cause PGAdapter to log an EOF warning. This error may be ignored. | ||
# The warning is caused by the TCP probe, which will open a TCP connection to PGAdapter, | ||
# but not send a PostgreSQL startup message, and instead just close the connection. | ||
startupProbe: | ||
initialDelaySeconds: 10 | ||
timeoutSeconds: 10 | ||
periodSeconds: 10 | ||
failureThreshold: 3 | ||
tcpSocket: | ||
port: 5432 |
108 changes: 108 additions & 0 deletions
108
...d-run/java/src/main/java/com/google/cloud/spanner/pgadapter/sample/SampleApplication.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
// Copyright 2023 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. | ||
|
||
package com.google.cloud.spanner.pgadapter.sample; | ||
|
||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.sql.Connection; | ||
import java.sql.DriverManager; | ||
import java.sql.PreparedStatement; | ||
import java.sql.ResultSet; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
|
||
/** | ||
* Sample application for connecting to Cloud Spanner using the PostgreSQL JDBC driver on Cloud Run. | ||
* This sample application runs PGAdapter as a sidecar container on Cloud Run. | ||
* | ||
* <p>Modify service.yaml to match your Cloud Run project and region, and your Cloud Spanner database. | ||
*/ | ||
@SpringBootApplication | ||
public class SampleApplication { | ||
|
||
@Value("${NAME:World}") | ||
String name; | ||
|
||
final String project = getEnvOrDefault("SPANNER_PROJECT", "my-project"); | ||
final String instance = getEnvOrDefault("SPANNER_INSTANCE", "my-instance"); | ||
final String database = getEnvOrDefault("SPANNER_DATABASE", "my-database"); | ||
final String qualifiedDatabaseName = | ||
String.format("projects/%s/instances/%s/databases/%s", project, instance, database); | ||
final String urlEncodedDatabaseName; | ||
final String pgadapterHost = getEnvOrDefault("PGADAPTER_HOST", "localhost"); | ||
final String pgadapterPort = getEnvOrDefault("PGADAPTER_PORT", "5432"); | ||
|
||
SampleApplication() { | ||
try { | ||
// We need to URL-encode the fully qualified database name before we can use it in a JDBC | ||
// connection URL. Otherwise, the JDBC driver will complain about the connection string | ||
// containing too many '/' characters. | ||
urlEncodedDatabaseName = URLEncoder.encode(qualifiedDatabaseName, StandardCharsets.UTF_8.name()); | ||
} catch (UnsupportedEncodingException exception) { | ||
throw new RuntimeException(exception); | ||
} | ||
} | ||
|
||
static String getEnvOrDefault(String key, String defaultValue) { | ||
return System.getenv(key) == null ? defaultValue : System.getenv(key); | ||
} | ||
|
||
@RestController | ||
class HelloworldController { | ||
@GetMapping("/") | ||
String hello() { | ||
String connectionUrl; | ||
if (pgadapterHost.startsWith("/")) { | ||
// Connect to PGAdapter using Unix Domain Sockets. This gives you the lowest possible | ||
// latency. The PGAdapter sidecar container and the main container both share the /sockets | ||
// directory, and PGAdapter is instructed to use this directory for Unix domain sockets. | ||
connectionUrl = String.format("jdbc:postgresql://localhost/%s?" | ||
+ "socketFactory=org.newsclub.net.unix.AFUNIXSocketFactory$FactoryArg" | ||
+ "&socketFactoryArg=%s/.s.PGSQL.%s", urlEncodedDatabaseName, pgadapterHost, pgadapterPort); | ||
} else { | ||
// Use a TCP connection. | ||
connectionUrl = String.format("jdbc:postgresql://%s:%s/%s", | ||
pgadapterHost, pgadapterPort, urlEncodedDatabaseName); | ||
} | ||
// NOTE: You should use a JDBC connection pool for a production application. | ||
try (Connection connection = DriverManager.getConnection(connectionUrl)) { | ||
// Create a prepared statement that takes one query parameter that will be used as the | ||
// name that will be greeted. | ||
try (PreparedStatement statement = | ||
connection.prepareStatement( | ||
"select 'Hello ' || ? || ' from Cloud Spanner using JDBC!' as greeting")) { | ||
statement.setString(1, name); | ||
try (ResultSet resultSet = statement.executeQuery()) { | ||
if (resultSet.next()) { | ||
return resultSet.getString(1) + "\n"; | ||
} else { | ||
return "No greeting was returned by Cloud Spanner!\n"; | ||
} | ||
} | ||
} | ||
} catch (Throwable exception) { | ||
return exception + "\n"; | ||
} | ||
} | ||
} | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(SampleApplication.class, args); | ||
} | ||
} |
1 change: 1 addition & 0 deletions
1
samples/cloud-run/java/src/main/resources/application.properties
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
server.port=${PORT:8080} |