Home > Software design >  How to run locally Docker Container with cloud-run service account & GOOGLE_APPLICATION_CREDENTIALS?
How to run locally Docker Container with cloud-run service account & GOOGLE_APPLICATION_CREDENTIALS?

Time:11-26

I am trying to run locally a Docker Container of a Ktor app that connects to a Cloud SQL database.

This container is later to be pushed to Cloud Run, so I'm using an IAM service account in my environment variables for the authentication. I've been researching this problem for a week and haven't found an answer.

The steps I used are the following:

  1. Create IAM service account "[email protected]
  2. Set the needed Roles for Cloud Run and Cloud SQL:
   Cloud Build Service Agent
   Cloud Run Admin
   Cloud Run Service Agent
   Cloud SQL Client
   Cloud SQL Instance User
   Viewer
  1. Create a new key from the IAM account and save it to desktop.
  2. Activate and login to the service account by running the following command on CMD:
 gcloud auth activate-service-account --key-file="C:\Users\user\Desktop\cloudrun-key.json"
  1. Set the GOOGLE_APPLICATION_CREDENTIALS environment variable to the key file location:
  set GOOGLE_APPLICATION_CREDENTIALS="C:\Users\user\Desktop\cloudrun-key.json"
  1. Navigate to my application folder and build the docker image:
 docker build -t my-application .
  1. Start the image:
 docker run -p 8080:8080 my-application

This is where I'm getting a vague exception: "Exception in thread "main" java.lang.NullPointerException".

To find what's going on I tried to use docker compose to see if I get a better idea. So I run the gradle shadowJar to create a .jar file in the build/libs directory, and then run "docker-compose up".

This is where I'm getting this exception that baffles me, since I have set the environment variable to the proper location:

Caused by: java.io.IOException: The Application Default Credentials are not available. They are available if running in Google Compute Engine. Otherwise, the environment variable GOOGLE_APPLICATION_ CREDENTIALS must be defined pointing to a file defining the credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

Dockerfile as instructed here:

FROM gradle:7-jdk11 AS build
# Copy local code to the container image.
COPY --chown=gradle:gradle . /home/gradle/src
WORKDIR /home/gradle/src
# Build a release artifact.
RUN gradle shadowJar --no-daemon 

FROM openjdk:11
EXPOSE 8080:8080
RUN mkdir /app
# Copy the jar to the production image from the builder stage.
COPY --from=build /home/gradle/src/build/libs/*.jar /app/my-application-docker.jar
# Run the web service on container startup.
ENTRYPOINT  [ "java", "-jar", "/app/my-application-docker.jar" ]

docker-compose.yml file:

services:
  web:
    build: .
    ports:
      - "8080:8080"
    environment:
      INSTANCE_CONNECTION_NAME: ${INSTANCE_CONNECTION_NAME}
      DB_IAM_USER: ${DB_IAM_USER}
      DB_USER: ${DB_USER}
      DB_NAME: ${DB_NAME}
      PORT: ${PORT}
      JDBC_DRIVER: ${JDBC_DRIVER}
      GOOGLE_APPLICATION_CREDENTIALS: ${GOOGLE_APPLICATION_CREDENTIALS}

env. file:

JDBC_DRIVER=org.postgresql.Driver
[email protected]
DB_NAME=my-database
INSTANCE_CONNECTION_NAME=foo-foo-121212:us-central1:database-sql-instance
PORT=8080
GOOGLE_APPLICATION_CREDENTIALS=C:\Users\user\Desktop\cloudrun-key.json

DatabaseFactory.kt file like this reference:

object DatabaseFactory {

    private val INSTANCE_CONNECTION_NAME = System.getenv("INSTANCE_CONNECTION_NAME");
    private val DB_IAM_USER = System.getenv("DB_IAM_USER")
    private val DB_NAME = System.getenv("DB_NAME")


    fun init() {
        Database.connect(hikari())

        transaction {
            SchemaUtils.create(FirstTable)
            SchemaUtils.create(SecondTable)
        }
    }


    private fun hikari(): HikariDataSource {

        val config = HikariConfig()

        // IAM AUTHENTICATION
        config.driverClassName = System.getenv("JDBC_DRIVER")
        config.jdbcUrl = String.format("jdbc:postgresql:///%s", DB_NAME)
        config.addDataSourceProperty("socketFactory", "com.google.cloud.sql.postgres.SocketFactory")
        config.addDataSourceProperty("cloudSqlInstance", INSTANCE_CONNECTION_NAME)
        config.addDataSourceProperty("enableIamAuth", "true")
        config.addDataSourceProperty("user", DB_IAM_USER)
        config.addDataSourceProperty("password", "password")
        config.addDataSourceProperty("sslmode", "disable")

        config.connectionTimeout = 10000 // 10s
        config.maximumPoolSize = 5
        config.minimumIdle = 3
        config.maxLifetime = 1800000; // 30 minutes
        config.isAutoCommit = false
        config.transactionIsolation = "TRANSACTION_REPEATABLE_READ"
        config.validate()

        // Initialize the connection pool using the configuration object.
        return HikariDataSource(config);


    }

    suspend fun <T> dbQuery(block: () -> T): T =
        withContext(Dispatchers.IO) {
            transaction {
                addLogger(StdOutSqlLogger)
                block()
            }
        }

}

Things that I have tried unsuccessfully:

  1. Testing a Cloud Run service locally . Getting NullPointerException

docker run -p 8080:8080 -v ${GOOGLE_APPLICATION_CREDENTIALS}:/tmp/keys/cloudrun-key.json:ro my-application-api

docker run -p 8080:8080 -e GOOGLE_APPLICATION_CREDENTIALS=/tmp/keys/cloudrun-key.json -v ${GOOGLE_APPLICATION_CREDENTIALS}:/tmp/keys/cloudrun-key.json:ro my-application-api

  1. Using my gmail user account to run the docker-compose up command by:
  • Un-setting the environment variable (set GOOGLE_APPLICATION_CREDENTIALS= )
  • gcloud auth login (to gmail account)
  • gcloud auth application-default login (to set the default credentials to known location)
  • running again the docker run command or docker-compose up

where I'm getting:

" Error reading credential file from environment variable GOOGLE_APPLICATION_CREDENTIALS, value 'C:\Users\user\Desktop\cloudrun-key.json': File does not exist."

  1. Setting the GOOGLE_APPLICATION_CREDENTIALS in Windows system environment variables manually through system properties. Again same error.

Any help would be great, thanks.

CodePudding user response:

Wow that's a lot of question :-)

First The following commands are mutually exclusive-ish:

This authenticates gcloud and its commands with the Service Account. When you run gcloud something, it will use the Service Account as its identity.

gcloud auth activate-service-account \
--key-file="C:\Users\user\Desktop\cloudrun-key.json"

This enables code built with Google's client libraries to use Application Default Credentials (ADC) to obtain the runtime identity for the code. It does not affect gcloud. It does provide an identity to your code.

I assume set is a Windows environment variable command

set GOOGLE_APPLICATION_CREDENTIALS="C:\Users\user\Desktop\cloudrun-key.json"

Second and I'll admit I scanned the question, you need to mount the service account into the container when it runs.

Using docker or podman etc.:

docker run \
--interactive --tty --rm
--publish=8080:8080 \
--volume=c:\Users\user\Desktop\cloudrun-key.json:/secrets/key.json \
--env=GOOGLE_APPLICATION_CREDENTIALS=/secrets/key.json \
my-application

The path c:\Users\user\Desktop\cloudrun-key.json is probably incorrect when using Docker. You may need to replace \ with /. You may need to do something with the c:. I don't know how this gets mapped on Windows.

The --volume flag maps the location of the key on your host to (:) a location in the container.

In this case, in the container, the key will be /secrets/key.json and so this is the value to be used for GOOGLE_APPLICATION_CREDENTIALS.

Using docker-compose it's going to be something like:

services:
  web:
    build: .
    ports:
      - "8080:8080"
    volumes:
    - "c:\Users\user\Desktop\cloudrun-key.json:/secrets/key.json"
    environment:
      INSTANCE_CONNECTION_NAME: ${INSTANCE_CONNECTION_NAME}
      DB_IAM_USER: ${DB_IAM_USER}
      DB_USER: ${DB_USER}
      DB_NAME: ${DB_NAME}
      PORT: ${PORT}
      JDBC_DRIVER: ${JDBC_DRIVER}
      GOOGLE_APPLICATION_CREDENTIALS: /secrets/key.json

Third when you run the container on Cloud Run, it will leverage ADC and automatically use the identity of the Cloud Run service that you deploy. Cloud Run services have a default identity but you can specify an identity at deployment with gcloud run deploy ... --service-account=${SERVICE_ACCOUNT}.

Thanks to ADC, you don't need to worry about mounting the key into the container or specifying an environment variable, it will just work.

  • Related