Deploy Docker Image to App Engine

Prabhu Gurung
readytowork, Inc.
Published in
5 min readApr 13, 2023
Docker Image on App Engine

Hello everyone, today we will be hosting a docker image to the Google App Engine instead of directly deploying projects made in languages supported by the service. Our system design will be as below.

System diagram

Our project will contain: Go project, Circle CI, and App Engine on the surface. We will also explore how the environment is orchestrated or services are managed by GCP during this article as we go.

Please note that we need to enable billing in this GCP project.

Let's get started.

Setup Go project:

Let's set up a Go project and create a simple Rest API. Create a folder and set up your project. I am creating a Go project as follows.

# This will create an app named web-service-gin
go mod init example/web-service-gin

# install go gin package
go add gin-gonic/gin
package main

import (
"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run()
}

We will be deploying this Go app to our app engine container.

Config Docker file

# FOR GAE Flexible Environment Custom Runtime 
FROM golang:1.17 AS BUILD

WORKDIR /usr/src/myapp
# Copy the code into the container\
COPY . .

# Copy and download dependency using go mod
RUN go mod download

# # Build the application
RUN go build -o myapp .



FROM golang:1.17
# Export necessary port # default GCP App Engine Port
WORKDIR /usr/myapp
COPY --from=BUILD usr/src/myapp/myapp usr/myapp
EXPOSE 8080

# Command to run when starting the container
CMD ["./usr/myapp"]

In the above docker file, we have used a multi-staged build to create our docker container. It. contains our app to be deployed.

The things that we need to be aware of during creating our custom runtime are:

  1. The App Engine front end will route incoming requests to the appropriate module on port 8080. So, we must be sure that your application code is listening to 8080.
  2. SIGTERM: When the container instance is about to shut down, the STOP (SIGTERM) signal is sent to our app. Acting on this signal is not mandatory but we can use it to start any clean-up process. After 30 sec of the signal, the KILL signal will be sent and the app will be stopped.
  3. Health check: The VM instance's health check is on by default. GAE uses Split Health Check. This health check is performed periodically and these requests must be handled gracefully by our runtime environment. More about the configs are discussed in the next steps.

Setup App Engine environment

Now let's create a file name app.yaml. Here, we will define the configuration for the app engine. We will use the app engine flex environment as the standard environment doesn’t support image deployment.

runtime: custom 
env: flex

service: app-engine-service
resources:
cpu: 1
memory_gb: 1

liveness_check:
check_interval_sec: 60
timeout_sec: 4
failure_threshold: 2
success_threshold: 1

readiness_check:
check_interval_sec: 90
timeout_sec: 6
failure_threshold: 3
success_threshold: 1
app_start_timeout_sec: 1000

automatic_scaling:
min_num_instances: 1
max_num_instances: 1

Here, we have configured our app engine with basic setup and resources. The caveat that we need to be aware regarding health-checkwould be liveness_check and readiness_check.
Liveness_check ensures that our container and VM are healthy and running and Readiness_check ensures that our container is capable of handling incoming requests. If any of these fails, the container will be set to unavailable, and either stopped or restarted. As the flex environment takes a little bit of time to set up the VM instances, I am giving enough room for the container to start our app. So, I am setting app_start_timeout_sec to 1000 sec.
According to our config, if our app doesn’t start in 1000 sec or it fails readiness_check for 3 times (threshold provided)which would be executed in intervals of 90sec, our container will stop and get an error.

Another thing to consider would be to provide automatic_scaling as well. I have tried without providing automatic scaling but I received an error 503 resources exhausted error. This was due to; by default, the config setup using 20 max instances where 15 was the max quota available. For more information, you can go through it here.

Setup Circle CI config

Let's create a Circle CI workflow in the folder .circleci/config.yml file.

First, let's create a Google Cloud Project and create a serviceAccountKey with proper access. For this please click here. It would guide you through creating serviceAccountKey.json.

After creating a project please add the following Env variables in circle CI. For adding an environment key guide please click here.
The env variables that we have declared are:
- GCLOUD_SERVICE_KEY (ServiceAccountKey.json)
- GOOGLE_PROJECT_ID
- GOOGLE_COMPUTE_ZONE

version: 2.1

machine:
jobs:
build-push-deploy:
docker:
- image: google/cloud-sdk:latest
steps:
- checkout
- run:
name: Authenticating and configuring the Google Cloud Platform
command: |
echo $GCLOUD_SERVICE_KEY | gcloud auth activate-service-account --key-file=-
gcloud --quiet config set project ${GOOGLE_PROJECT_ID}
gcloud --quiet config set compute/zone ${GOOGLE_COMPUTE_ZONE}
- run:
name: Deploy docker image to app engine
command: |
gcloud app deploy app.yaml

workflows:
build-and-deploy:
jobs:
- build-push-deploy:
filters:
branches:
only:
- main

This is the circleci config file for workflow. I think the steps are self-explanatory however I will describe the steps in brief.

Docker environment:

Image -> google/cloud-sdk:latest. This is an official docker image that has Gcloud CLI included. It enables us to connect, control services or deploy our apps to Google service.

Checkout:

It checks out the source code to the configured path (defaults to the working_directory).

Authentication and configuration:

Here we are authenticating gcloud CLI to our resources using serviceAccountKey.json.

Deploy to App Engine:

In this step, we will deploy the project to our App Engine. During the Gcloud app deployment process following processes are happening in the background:

  1. The docker image is built from .dockerfile provided.
  2. The docker image is pushed to GCR (Google Container Registry). It is a place where Docker images are managed. GCR uses Cloud storage for storing the image artifacts.
  3. After the images are pushed and updated, a VM instance is created and the app is deployed.

Conclusion

If everything goes well, we should have deployed our service to App Engine successfully. If any issues arise, we can use Google Log Explorer for identifying the underlying reason.

--

--