Deploying to Google App Engine Using Github Actions

Multi Environment deployment with Google App Engine and Github Actions | by  Rafael Etereo | Etéreo

What is CI/CD?

CI and CD stand for continuous integration and continuous delivery/continuous deployment. In very simple terms, CI is a modern software development practice in which incremental code changes are made frequently and reliably. Automated build-and-test steps triggered by CI ensure that code changes being merged into the repository are reliable. The code is then delivered quickly and seamlessly as a part of the CD process. In the software world, the CI/CD pipeline refers to the automation that enables incremental code changes from developers’ desktops to be delivered quickly and reliably to production.

Google App Engine is a fully managed, serverless platform for developing and managing applications at scale. With App Engine, the developer does not need to worry about the infrastructure needed to deploy the application and only focuses on his code while App Engine handles the deployment.

When you combine Google App Engine and Github Actions, it provides a seamless deployment process.

In this article, we will explore how to automate the deployment of a React and Java application to Google App Engine using GitHub Actions.

Step 1: Setting Up Your Google Cloud Project

Before we begin, you should have your Google Cloud project set up with App Engine API enabled. Take note of your Project ID as you will need it later.

Step 2: Configuring Github Secrets

For us to authenticate Google App Engine with GitHub Actions, we need to pass it credentials and these credentials should be stored using Github secrets.

Adding Secrets to Github

  1. In your GitHub repository, go to Settings.

  2. Under Security, find and select Secrets and Variables.

  3. Click on Actions.

  4. Click on New repository secret.

  5. Name your secret GCP_SA_KEY and paste the content of your Google Cloud service account key JSON file.

  6. Add another secret named GCP_PROJECT_ID and set it to your Google Cloud project ID.

  7. Add any other additional secret you need.

NB: The React Application only needs the GCP_PROJECT_ID and GCP_SA_KEY . All other secrets are for the Java Application

Step 3: Writing the GitHub Action Workflow (React App)

Create a .github/workflows/deploy.yml file in the root of your repository with the following content:

name: Deploy and Test Workflow

on:
  push:
    branches:
      - dev
  pull_request:
    branches:
      - dev

jobs:
  test:
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Clear npm cache
        run: npm cache clean --force

      - name: Install dependencies
        run: npm install

      - name: Run tests
        run: npm test

  deploy:
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Setup Google Cloud CLI
        uses: google-github-actions/setup-gcloud@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          service_account_key: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      - name: Install dependencies and build
        run: |
          npm install
          npm run build

      - name: Deploy to Google App Engine
        uses: google-github-actions/deploy-appengine@v0.2.0
        with:
          deliverables: app.yaml
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          credentials: ${{ secrets.GCP_SA_KEY }}
          promote: true
          version: v1

This YAML file defines a GitHub Actions workflow with two jobs:

  • test: This job installs the project dependencies and runs tests. It triggers on pull requests to the dev branch.

  • deploy: This job installs dependencies, builds the project, and deploys to Google App Engine. It triggers after a merge to the dev branch.

Step 4: Configuring app.yaml

Ensure you have an app.yaml file in your project root that specifies the runtime and other configurations for App Engine.

Here is the app.yaml for the React app:

runtime: nodejs18
handlers:
# Serve all static files with url ending with a file extension
- url: /(.*\..+)$
  static_files: dist/\1
  upload: dist/(.*\..+)$
# Catch all handler to index.html
- url: /.*
  static_files: dist/index.html
  upload: dist/index.html

instance_class: F1
automatic_scaling:
  target_cpu_utilization: 0.65

entrypoint: npm start

env_variables:
  PORT: 5317

Here is a breakdown of each part:

    1. runtime: nodejs18: Specifies that the runtime environment for the application is Node.js version 18.

      1. handlers: Defines URL patterns and how they should be served:

        • The first handler - url: /(.*\..+)$ is a regex that matches any URL ending with a file extension. This means any request to a URL that looks like it's for a static file (like styles.css, script.js, image.png, etc.) will be served from the dist directory where the static files are assumed to be compiled and stored.

          • static_files: dist/\1 and upload: dist/(.*\..+)$ direct App Engine to serve the files from the dist directory and upload all files matching the given pattern when deploying.
        • The second handler - url: /.* is a catch-all pattern that matches all other URLs and serves index.html from the dist directory. This is typical for single-page applications (SPAs) that handle routing on the client side.

      2. instance_class: F1: Specifies the instance class for the app. F1 is a specific class of machine that defines the amount of CPU and memory allocated to the instance.

      3. automatic_scaling: Defines the scaling behavior of the application.

        • target_cpu_utilization: 0.65 means that the app will scale to maintain a target CPU utilization of 65%.
      4. entrypoint: The command that is executed to start the application, which in this case is npm start.

      5. env_variables: Defines environment variables for the application.

        • PORT: 5317 specifies that the app should listen on port 5317.

The app.yaml configuration is important because it ensures that the React application is served efficiently on Google App Engine, with automatic scaling to handle varying loads, optimized caching for static assets, and basic security headers to enhance the security posture of the application.

Step 5: Writing the GitHub Action Workflow (Java App)

Create a .github/workflows/deploy.yml file in the root of your repository with the following content:

name: Deploy and Test Workflow

on:
  push:
   branches:
     - dev  # Triggers the workflow on push events to dev branch.
  pull_request:
    branches:
      - dev # Triggers the workflow on pull request events targeting dev branch.

jobs:
  test:
    # This job runs on pull_request events
    if: github.event_name == 'pull_request'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      # Add steps for testing your java app
      - name: Run tests
        run: mvn clean install -DskipTests

  build_and_deploy:
    # This job runs on push events, specifically after PRs are merged into main
    # needs: test
    if: github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v3

      - name: Set up Java
        uses: actions/setup-java@v2
        with:
          java-version: '17' 
          distribution: 'adopt'

      - name: Set environment variables from secrets
        run: |
          echo "DB_URL=${{ secrets.DB_URL }}" >> $GITHUB_ENV
          echo "GOOGLE_HOST=${{ secrets.GOOGLE_HOST }}" >> $GITHUB_ENV
          echo "GOOGLE_USERNAME=${{ secrets.GOOGLE_USERNAME }}" >> $GITHUB_ENV
          echo "GOOGLE_PASSWORD=${{ secrets.GOOGLE_PASSWORD }}" >> $GITHUB_ENV
          echo "GOOGLE_PORT=${{ secrets.GOOGLE_PORT }}" >> $GITHUB_ENV
          echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> $GITHUB_ENV
          echo "GOOGLE_CLIENT_ID=${{ secrets.GOOGLE_CLIENT_ID }}" >> $GITHUB_ENV

      - name: Setup Google Cloud CLI
        uses: google-github-actions/auth@v1
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          credentials_json: ${{ secrets.GCP_SA_KEY }}
          export_default_credentials: true

      - name: Run tests
        run: mvn clean install -DskipTests

      - name: Build with Maven
        run: mvn spring-boot:run

      - name: Deploy to Google App Engine
        uses: google-github-actions/deploy-appengine@v0
        with:
          project_id: ${{ secrets.GCP_PROJECT_ID }}
          credentials: ${{ secrets.GCP_SA_KEY }}
          promote: true
          version: 'auto' # Automatically generate a version ID
          deliverables: target/APP-NAME-0.0.1-SNAPSHOT.jar # Update this to the path of your built JAR

Step 6: Configuring app.yaml (Java App)

Ensure you have an app.yaml file in your project root that specifies the runtime and other configurations for App Engine.

Here is the app.yaml for the Java app:

runtime: java17
env: standard
instance_class: F1  # Smallest instance for development to minimize costs.

automatic_scaling:  # Use automatic scaling for development to manage instances more flexibly.
  max_instances: 1  # Limit instances to minimize costs.
  min_idle_instances: 0  # Scale down to 0 instances when not in use.
  max_idle_instances: 1  # Limit to 1 to prevent cost from idle instances.
  max_concurrent_requests: 10  # Limit to prevent overuse by too many requests.

handlers:
  - url: .*
    script: auto

# Set environment-specific variables if needed.
env_variables:
  ENV: 'dev'
  1. runtime: java17: This indicates the application is using Java 11 as the runtime environment.

  2. env: standard: The app will run in the Standard Environment, which is a preconfigured environment with a specific set of available languages and third-party libraries.

  3. instance_class: F1: This specifies the class of machine that the service will use. The F1 class is the smallest available instance type in App Engine, with the lowest amount of CPU and memory. It is a cost-effective option for a development environment where you don’t expect heavy traffic.

  4. automatic_scaling: This section configures how App Engine automatically adjusts the number of running instances based on the current load.

    • max_instances: 1: Sets the maximum number of instances to 1. This means only one instance of the app will be running at any time, which is usually sufficient for development purposes.

    • min_idle_instances: 0: Allows App Engine to scale down to 0 instances when there is no traffic, saving costs.

    • max_idle_instances: 1: Limits the number of idle instances to 1 to minimize running costs for unused resources.

    • max_concurrent_requests: 10: Limits the number of requests that an instance can process simultaneously to 10. This is a conservative limit that helps to avoid overloading the development instance.

  5. handlers:

    • - url: .*: This handler definition states that any URL (.* is a regular expression that matches any string) should be served by the application.

    • script: auto: In the Java 11 runtime, this field is ignored since it is more relevant to runtimes like Python or PHP, where you map URLs to specific script files.

  6. env_variables: This section is used to define environment variables that the application can access at runtime.

    • ENV: 'dev': Sets an environment variable ENV to the string dev, which can be useful to alter behavior or settings when running in a development environment.

Step 5: Merging and Observing the Deployment

Once you merge your changes into the dev branch, the deploy job will start:

  1. GitHub Actions checks out the latest code.

  2. It sets up the Google Cloud CLI with the provided credentials.

  3. The project dependencies are installed, and the build script is run.

  4. If everything is successful, the application is deployed to Google App Engine.

Conclusion

GitHub Actions is easy and simple to use once you get a hang of some of its quirks, coupled with cloud serverless cloud offerings creating a CI/CD workflow is easy. With these steps completed, you should have a fully functional CI/CD pipeline that automatically deploys your React and Java application to Google App Engine using GitHub Actions.

References

  1. Quickstart for GitHub Actions - GitHub Docs

  2. App Engine documentation | App Engine Documentation | Google Cloud

  3. Deploy to App Engine using Github Actions

  4. GitHub - google-github-actions/setup-gcloud: A GitHub Action for installing and configuring the gcloud CLI.