0

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
    }
}
Myles W
  • 63
  • 1
  • 6
  • Did you try to deploy by commenting the resource line `spring.cloud.gcp.credentials.location`? – guillaume blaquiere Mar 18 '21 at 08:04
  • I did, yes. This fails when I attempt to deploy with the credentials location specified as a property, and without – Myles W Mar 18 '21 at 14:46
  • Do you have a minimal code sample to reproduce the issue? – guillaume blaquiere Mar 18 '21 at 15:19
  • @guillaumeblaquiere added the application-gcp.yaml and build.gradle.kts files. Is there anything else, implementation-wise, you would find relevant? Thank you for helping with this! – Myles W Mar 18 '21 at 23:12
  • And you never use cred file in your code? It fails at startup, correct? – guillaume blaquiere Mar 20 '21 at 13:11
  • @guillaumeblaquiere correct, it fails at startup, both when I include the credentials file in the resources directory, and when I exclude it from the build. – Myles W Mar 23 '21 at 22:51
  • Did you try running `gcloud auth application-default login` to set the authentication variables? The application will look for these credentials automatically if GOOGLE_APPLICATION_CREDENTIALS is not set. – Roger Mar 31 '21 at 09:07
  • Please see my "solution" to this issue below. Thank you both for your help and advice on this, though. I really appreciate it! – Myles W Apr 10 '21 at 20:53

1 Answers1

1

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.

Myles W
  • 63
  • 1
  • 6