How to Run a CUBA App in Google App Engine Flexible

UPDATE: The manual and sample application was updated to CUBA 7 and the latest GAE SDK version. Now the manual is in the sample’s README file here: https://github.com/cuba-labs/gaef-sample

Thanks to @nelsonflorez and @marc for the contribution!

The previous version

Here are steps to deploy and run the CUBA application in Google App Engine Flexible.

Install the Cloud SDK. See Google Cloud CLI documentation  |  Google Cloud CLI Documentation. Also, install the app-engine-java component: gcloud components install app-engine-java.

Create a new project using the GCP Console. I named it cuba-sample.

Selection_015

Create an application using Java language.

Selection_021

Specify a region. In my case it’s the ‘europe-west-3’.

Selection_022

Create a new PostgreSQL Cloud SQL instance. I gave the instance an id cuba-sample-db. Specify a default password for the postgres user (I typed postgres) and select the region.

Selection_016

Dive into the DB instance settings and create a new database. In my case its name is “gaef”.

Selection_017

As stated in this documentation section you’ll need to enable the Cloud SQL Admin API. Follow the “Enable API” link from this section and select a “cuba-sample” project in the appeared dialog. Click “Continue”.

Selection_018

Create a new CUBA project using CUBA Studio. In my case, its name is gaef-sample.

Open the Project properties page and change the database type to PostgreSQL.

Click the Deployment settings button and specify Uber JAR deployment options:

  • Check the ‘Build Uber JAR’ checkbox
  • Generate the Logback configuration file
  • Generate custom Jetty environment file. In the dialog leave the database connection settings as is. We’ll modify them later in the file.

Selection_019

Open the jetty-env.xml file in the IDE.

The database URL should conform the format described in the manual, i.e.:

jdbc:postgresql://google/${database}?useSSL=false&cloudSqlInstance=${INSTANCE_CONNECTION_NAME}&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=${user}&password=${password}

INSTANCE_CONNECTION_NAME here can be found in the Cloud SQL instance overview page:

Selection_020

In my case the URL will be:

jdbc:postgresql://google/gaef?useSSL=false&cloudSqlInstance=cuba-sample:europe-west3:cuba-sample-db&socketFactory=com.google.cloud.sql.postgres.SocketFactory&user=postgres&password=postgres

Write it to the jetty-env.xml file and comment the username and password tags:

<?xml version="1.0"?>
<!DOCTYPE Configure PUBLIC "-" "http://www.eclipse.org/jetty/configure_9_0.dtd">
<Configure id='wac' class="org.eclipse.jetty.webapp.WebAppContext">
    <New id="CubaDS" class="org.eclipse.jetty.plus.jndi.Resource">
        <Arg/>
        <Arg>jdbc/CubaDS</Arg>
        <Arg>
            <New class="org.apache.commons.dbcp2.BasicDataSource">
                <Set name="driverClassName">org.postgresql.Driver</Set>
                <Set name="url">jdbc:postgresql://google/gaef?useSSL=false&amp;cloudSqlInstance=cuba-sample:europe-west3:cuba-sample-db&amp;socketFactory=com.google.cloud.sql.postgres.SocketFactory&amp;user=postgres&amp;password=postgres</Set>
                <!--<Set name="username">postgres</Set>-->
                <!--<Set name="password">postgres</Set>-->
                <Set name="maxIdle">2</Set>
                <Set name="maxTotal">20</Set>
                <Set name="maxWaitMillis">5000</Set>
            </New>
        </Arg>
    </New>
</Configure>

Create the app.yaml file in the appengine directory in the root of the project:

runtime: java
runtime_config:
  jdk: openjdk8
env: flex
manual_scaling:
  instances: 1
readiness_check:
  app_start_timeout_sec: 1500
env_variables:
  JAVA_OPTS: "-Dapp.home=/opt/app_home"

An important thing here is the JAVA_OPTS environment variable. It seems that uber jar is copied to the root of the docker container. This case causes some relative URL problems when Jetty server starts. To avoid these problems we have to specify an application home directory.

app_start_timeout_sec is increased because sometimes the deployment on my side took more time than the default time.

I did the deployment using the Gradle plugin. IntelliJ plugin also worked.

Add a dependency to the appengine-gradle-plugin in the build.gradle:

    dependencies {
        classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.+' // Latest 1.x.x release
        classpath "com.haulmont.gradle:cuba-plugin:$cubaVersion"
    }

In the end of the build.gradle file add necessary gradle tasks:

apply plugin: 'com.google.cloud.tools.appengine'

appengine {
    stage {
        artifact = "$buildDir/distributions/uberJar/app.jar"
        appEngineDirectory = 'appengine'    // a directory with app.yaml
        stagingDirectory = "$buildDir/staged-app"
    }

    deploy {
        project = 'cuba-sample'     // specify a project id if the current project is not the default one
        stopPreviousVersion = true  // default - stop the current version
        promote = true              // default - & make this the current version
    }
}

appengineStage.dependsOn(buildUberJar)

// a dummy task. It is required for appengineStage task of the google plugin
task assemble {
    doLast {}
}

In the dependencies section of the coreModule add a dependency to the postgres-socket-factory:

    dependencies {
        compile(globalModule)
        provided(servletApi)
        jdbc(postgres)
        jdbc('com.google.cloud.sql:postgres-socket-factory:1.0.11')
        testRuntime(postgres)
    }

Change the PostgreSQL JDBC driver version:

def postgres = 'org.postgresql:postgresql:42.2.5'

Then run the appengineDeploy gradle task and the project should be deployed:

./gradlew appengineDeploy

After the deployment is completed, open the application URL in the browser and add the /app to the end of the URL:

http://cuba-sample.appspot.com/app

An important note here. You will not be able to save files using the standard FileStorage. If your application uses it, then you 'll have to do something with files persistence. At first look, there are several options here: create your own file storage implementation similar to the Amazon S3 File Storage, but using the Google Cloud Storage. Another option you can try is to mount the NFS directory.

There is a GitHub project with the CUBA app I’ve got after completion of all the steps above: GitHub - cuba-labs/gaef-sample: Google App Engine Flexible Deployment. There are several commits there. The first one just commits the app created by Studio, the second commit contains all modifications required for deployment.

5 Likes

Hi,

I wrote a simple GCS File Storage for CUBA which I use, I posted it on a Gist to complement this post: Example Google Cloud Storage for CUBA

2 Likes

Hi Marc,
Thank you for sharing it!

1 Like

It is possible to update the example to the current version of CUBA?

Regards,

Nelson F.

I’ve been working on adapting the deployment. So far the changes made in build.gradle are as follows

buildscript {
    ext.cubaVersion = '7.2.4'
    repositories {
        maven {
            url 'https://dl.bintray.com/cuba-platform/main'
            
        }
        jcenter()
        google()
        
    }
    dependencies {
        classpath "com.haulmont.gradle:cuba-plugin:$cubaVersion"
        classpath 'com.google.cloud.tools:appengine-gradle-plugin:2.+'
    }
}
task buildUberJar(type: CubaUberJarBuilding) {
    logbackConfigurationFile = 'etc/uber-jar-logback.xml'
    singleJar = true
    appProperties = ['cuba.automaticDatabaseUpdate': true,                    
                     'cuba.web.loginDialogDefaultUser'    : '',
                     'cuba.web.loginDialogDefaultPassword': '',
                     'cuba.dataSource.username'    : 'user',
                     'cuba.dataSource.password'    : 'password',
                     'cuba.dataSource.dbName'      : 'db',
                     'cuba.dataSource.host'        : 'private_ip_addres',
                     'cuba.dataSource.port'        : '',
                     'cuba.dataSourceProvider'     : 'application']
}

//GAE
apply plugin: 'com.google.cloud.tools.appengine'

appengine {
    stage {
        artifact = "$buildDir/distributions/uberJar/app.jar"
        appEngineDirectory = 'appengine'    //a directory with app.yaml
        stagingDirectory = "$buildDir/staged-app"
    }

    deploy {
        stopPreviousVersion = true  // default - stop the current version
        promote = true              // default - & make this the current version
        projectId = 'GCLOUD_CONFIG' // specify project if the current project is not the default one
        version = 'GCLOUD_CONFIG'  // delegate to gcloud to generate a version
    }
}

appengineStage.dependsOn(buildUberJar)

//dummy task. It is required for appengineStage task of the google plugin
task assemble {
    doLast {}
}

Private ip address must be generated for the database server instance. Now, app.yaml file has the following changes according to my needs. For each project you can consult the official documentation and adapt it.

runtime: java11 # name of the runtime environment your app uses
automatic_scaling:
  target_cpu_utilization: 0.85 # specifies the CPU usage limit at which new instances will be started
  target_throughput_utilization: 0.85 # when a new instance is started due to simultaneous requests

I have a problem that I don’t know what it is and I would like you to help me: When the appengineDeploy task is executed, an exception is generated

ERROR: (gcloud.app.deploy) Error Response: [9] Cloud build XXXX status: FAILURE.
Build error details: {"error":{"buildpackId":"google.java.appengine","buildpackVersion":"0.9.0","errorType":"UNKNOWN","canonicalCode":"UNKNOWN","errorId":"838926df","errorMessage":"did not find any jar files with a Main-Class manifest entry"},"stats":null}.


Execution failed for task ':appengineDeploy'.
> com.google.cloud.tools.appengine.AppEngineException: com.google.cloud.tools.appengine.operations.cloudsdk.process.ProcessHandlerException: com.google.cloud.tools.appengine.AppEngineException: Non zero exit: 1

I understand by informing me “did not find any jar files with a Main-Class manifest entry” that I must set a class as main, but in this part if I get lost. I have not worked with a uberJar file before. Any suggestions?

I still have two points to solve like addressing fts.indexDir (I have no idea) and cuba.fileStorageDir (I will be guided by the contribution of @marc )

Best regards,

Nelson F.

Hi,

We have an open issue at the moment: buildUberJar create empty manifest file when using Java 8 · Issue #140 · cuba-platform/cuba-gradle-plugin · GitHub

Please check that you are not using Java 8 to build the project, switch to Java 11 if so.
UberJar can be opened like a ZIP file to view its contents. Manifest file is located in the META-INF directory. See also Understanding the Default Manifest (The Java™ Tutorials > Deployment > Packaging Programs in JAR Files)

Consistency of the UberJar can be checked by launching it from the command line:

java -jar app.jar

Hi @AlexBudarov, Thank you if that was it, now I have the next mistake

Some environment checks failed on core module:
Directory 'cuba.dataDir' must have read and write permissions. Current permissions: Readable: false, Writable: false, Is directory: false
Directory 'cuba.tempDir' must have read and write permissions. Current permissions: Readable: false, Writable: false, Is directory: false
INFO  c.h.c.c.s.p.PersistenceConfigProcessor  - Creating file /workspace/app-core/work/persistence.xml
default[20200522t063629] 
ERROR c.h.c.c.s.AbstractWebAppContextLoader   - Error initializing application

I don’t know if it’s permissions within the app engine or if I’m missing something to configure.
Anyway, to whomever can give me a hand, I appreciate it

Regards,

Nelson F.

CUBA needs some directory to write temporary files - it’s called application home.
There’s information in the Google AppEngine docs that the only writeable directory is “/tmp”.

So you should try to add runtime argument for the app.yaml env_variables:
-Dapp.home=/tmp/
or
-Dapp.home=/tmp/cuba

Solved all problem, according to documentation. New lines added in app.yalm file

entrypoint: "java -Dapp.home=/tmp/cuba -jar app.jar"
vpc_access_connector:
  name: "projects/[PROJECT_ID]/locations/[REGION]/connectors/[CONNECTOR_NAME]"
1 Like

The manual and sample application was updated to CUBA 7 and the latest GAE SDK version. Now the manual is in the sample’s README file here: GitHub - cuba-labs/gaef-sample: Google App Engine Flexible Deployment

Thanks to @nelsonflorez and @marc for the contribution!

2 Likes