Myles W
Myles W

Reputation: 63

Spring Boot GCP: "Google Credentials" Exception When Deploying PubSub Application to App Engine Standard Environment

I am attempting to deploy a Spring Boot application to GCP's App Engine Standard environment. The application is able to receive published messages from PubSub while running locally on my dev machine. I have configured the application to authenticate with service credentials via the $GOOGLE_APPLICATON_CREDENTIALS environment variable.

However, when I attempt to publish this application to App Engine, and then subsequently tail the logs (via gcloud app logs tail -s test-app-service), I see the following error: Factory method 'googleCredentials' threw exception; nested exception is java.io.FileNotFoundException: src/main/resources/app-engine-service-creds.json (No such file or directory)

And the application fails to start. This happens both when I run the gcloud deploy CLI command:

gcloud app deploy build/libs/test-app-*.jar --appyaml=src/main/appengine/app.yaml

as well as the Gradle GCP plugin task:

./gradlew appengineDeploy

This error also occurs when I include the JSON key file in src/main/resources and reference it in my application.yaml file with the spring.cloud.gcp.credentials.location argument.

There is shockingly little documentation about actually deploying Spring Boot applications to App Engine, and I am out of ideas here.

app.yaml:

runtime: java11
service: "test-app-service"
env_variables:
  SPRING_PROFILES_ACTIVE: "gcp"

Any and all suggestions here would be greatly appreciated. Thank you!

Edit Additional (potentially) relevant files: application-gcp.yaml

spring:
  cloud:
    gcp:
      project-id: "my-project"

build.gradle.kts

import com.google.cloud.tools.gradle.appengine.appyaml.AppEngineAppYamlExtension
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath("com.google.cloud.tools:appengine-gradle-plugin:2.2.0")
    }
}

plugins {
    id("org.springframework.boot") version "2.4.1"
    id("io.spring.dependency-management") version "1.0.10.RELEASE"
    id("org.jetbrains.kotlin.plugin.allopen") version "1.4.21"
    kotlin("jvm") version "1.4.21"
    kotlin("plugin.spring") version "1.4.21"
}

group = "com.myGroup"
java.sourceCompatibility = JavaVersion.VERSION_11

if (project.hasProperty("projVersion")) {
    project.version = project.properties["projVersion"]!!
} else {
    project.version = "1.0.0"
//  throw Exception("Project Version must be passed in ex.   ./gradlew clean build -PprojVersion=1.0.0")
}

repositories {
    mavenCentral()
    jcenter()
    maven { url = uri("https://repo.spring.io/milestone") }
}

apply {
    plugin("com.google.cloud.tools.appengine")
}

// exclude the app-engine-service-creds
sourceSets {
    main {
        resources {
            exclude("app-engine-service-creds.json")
        }
    }
}

extra["springCloudGcpVersion"] = "2.0.0-RC2"
extra["springCloudVersion"] = "2020.0.0-M6"

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-webflux")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.springframework.boot:spring-boot-starter-data-redis-reactive")
    implementation("com.google.cloud:spring-cloud-gcp-starter-pubsub")
    implementation("org.springframework.boot:spring-boot-starter-integration")
    implementation("io.projectreactor.kotlin:reactor-kotlin-extensions")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    implementation("io.github.microutils:kotlin-logging:1.12.0")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
    testImplementation("org.amshove.kluent:kluent:1.64")
    testImplementation("io.projectreactor:reactor-test")
    testImplementation("org.springframework.integration:spring-integration-test")
}

dependencyManagement {
    imports {
        mavenBom("com.google.cloud:spring-cloud-gcp-dependencies:${property("springCloudGcpVersion")}")
        mavenBom("org.springframework.cloud:spring-cloud-dependencies:${property("springCloudVersion")}")
    }
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "11"
    }
}

tasks.withType<Test> {
    useJUnitPlatform()
}

configure<AppEngineAppYamlExtension> {
    deploy {
        projectId = "my-project"
        version = "GCLOUD_CONFIG"
        stopPreviousVersion = true // etc
    }
}

Upvotes: 0

Views: 1256

Answers (1)

Myles W
Myles W

Reputation: 63

As it turns out, the tailed logs returned from the gcloud app logs tail -s {service-name} command can be a bit of a red herring. Although I was deploying a new version of the application, and all traffic is 100% directed toward this new instance, the app does not "startup" until it receives a request. For reasons that I still do not fully understand, the logs were tailing a very stale version of the application (and I'm not the only one who has been confounded by this, obviously: "gcloud app logs tail" shows week old data).

In reality, the app was not experiencing any credentials issues after all, but was merely running out of memory on startup, and thus, I was unable to verify that the messages had been pulled successfully from PubSub by my applcation.

I fixed this by add the following parameter to my app.yaml filed: instance_class: f4

The problem has since resolved itself.

Upvotes: 1

Related Questions