How to deploy a Golang Application with Google cloud run

Google cloud run is a fully managed container execution environment. It is an environment specifically for request-driven workloads. It provides autoscaling, scaling down to zero, pretty fast deployments, automatic HTTPS support, global names and more. Google Cloud Run doesn’t have language runtime restrictions as soon as the language runtime is supported on gVisor. It only requires the deployments to expose an HTTP server on a port.

This guide assumes that you are familiar with cloud console and cloud shell environment.

In this guide, we will deploy a minimal helloworld HTTP server. The container runtime is expecting the server to listen on $PORT:

Related contents:

Google cloud Authentication and Enabling required APIs

Since we will deploy our code to google cloud, we will need to authenticate to it. In this guide we will use the command line to achieve most of what we want. Google provides a command line utility (gcloud) that allows you to perform most of the operations with the google cloud.

You will need to have gcloud installed. If not, consult the gcloud installation page. Confirm that it is working as expected by checking the version installed:

1
2
3
4
5
6
7
8
9
$ gcloud version
Google Cloud SDK 414.0.0
alpha 2023.01.13
beta 2023.01.13
bq 2.0.84
core 2023.01.13
gcloud-crc32c 1.0.0
gke-gcloud-auth-plugin 0.4.0
gsutil 5.18

Since we will be using cli to perform out operations, we need to authenticate to google cloud. Use this gcloud command:

1
gcloud auth login

Then do application login to generate credentials for client libraries:

1
gcloud auth application-default login

Once logged in, we need to enable the required APIs. In our case, we will use cloudbuild so enable it with this command:

1
2
3
4
5
6
7
export GCP_PROJECT_NAME=citizix-build-project

# We can use this to build and submit container image
gcloud services enable cloudbuild.googleapis.com --project $GCP_PROJECT_NAME

# We will use this to run our code
gcloud services enable run.googleapis.com --project $GCP_PROJECT_NAME

That is it with google authentication, we are now set to create out application and deploy to cloud build.

Creating a Golang Application

Before checking this section, ensure that you have golang installed and working as expected. Checkout the go downloads page if you need any assistance with that.

Once installed, confirm that it is working as expected by checking the version. This is the output on my machine:

1
2
3
$ go version

go version go1.22.1 darwin/arm64

Now we can set up a directory and add out code. Create a new application directory and initialize it as a Go application using this command:

1
2
3
4
5
mkdir goapp

cd goapp

go mod init goapp

Inside the directory, we will create a file main.go and add the following content

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package main

import (
  "errors"
  "fmt"
  "io"
  "log"
  "net/http"
  "os"
)

func main() {

  mux := http.NewServeMux()

  mux.HandleFunc("/", handleRoot)

  log.Println("Starting server on 8080")

  err := http.ListenAndServe(":8080", mux)
  if errors.Is(err, http.ErrServerClosed) {
    fmt.Printf("server closed\n")
  } else if err != nil {
    fmt.Printf("error starting server: %s\n", err)
    os.Exit(1)
  }
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
  io.WriteString(w, "Hello world!\n")
}

The above code will spin up a web server listening on port 8080 that responds with Hello world on GET request to /. To test that it is working as expected, run it with the following command:

1
2
3
$ go run main.go

2023/01/20 09:03:22 Starting server on 8080

On a separate terminal window, you can make request to our server using curl. You should see something similar to this:

1
2
$ curl http://localhost:8080/
Hello world!

Dockerize the app

Cloud run expects a container. That means for out application to work it needs to be dockerized. As a prerequisite, you need to have docker installed and working in your machine. If not, checkout How to install and configure docker in Rocky Linux/Alma Linux 9.

Confirm that docker is working as expected by checking the version installed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ docker version

Client:
 Cloud integration: v1.0.29
 Version:           20.10.21
 API version:       1.41
 Go version:        go1.18.7
 Git commit:        baeda1f
 Built:             Tue Oct 25 18:01:18 2022
 OS/Arch:           darwin/arm64
 Context:           default
 Experimental:      true

Next we will dockerize the app. Create a file named Dockerfile in the application directory and add the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Compile stage
FROM golang:1.22.1-alpine AS compiler
# Add required packages
ENV CGO_ENABLED 0 \
    GOOS=linux
WORKDIR /app
ADD go.mod ./
RUN go mod download
ADD . .
RUN go build -o goapp

# Run stage
FROM alpine:3.19
WORKDIR /usr/src/app
COPY --from=compiler /app/goapp .
CMD ["/usr/src/app/goapp"]

To test that this will build as expected use this command:

1
docker build . -t goapp:latest

Pushing the image to Google Cloud Registry

Once we have our application container image built, we need to push it to GCR for cloud run to use.

If you want to use the docker commandline, authenticate to docker registry:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$ gcloud auth configure-docker gcr.io

Adding credentials for: gcr.io
After update, the following will be written to your Docker config file located at
[/Users/citizix/.docker/config.json]:
 {
  "credHelpers": {
    "gcr.io": "gcloud"
  }
}

Do you want to continue (Y/n)?

Docker configuration file updated.

Build and push the image to Google Container Registry using the following commands:

Build the docker image

1
docker build -t gcr.io/$GCP_PROJECT_NAME/goapp:0.1.0 .

Push the docker image to container registry

1
docker push gcr.io/$GCP_PROJECT_NAME/goapp:0.1.0

The other alternative is using gcloud builds submit specifying your image as shown in this command:

1
2
3
gcloud builds submit \
  --project $GCP_PROJECT_NAME \
  --tag gcr.io/$GCP_PROJECT_NAME/goapp:0.1.0

This command builds a container with your code and puts it in the Container Registry of your project. You can see the container if you click: Navigation menu > Container Registry. If you don’t see goapp, click Refresh.

Deploying our application to cloud build

We can use the earlier image to deploy our application on cloud build:

1
2
3
4
5
gcloud run deploy goapp --project $GCP_PROJECT_NAME \
    --image=gcr.io/$GCP_PROJECT_NAME/goapp:0.1.0 \
    --region=europe-west1 \
    --allow-unauthenticated \
    --max-instances=2

You can also build and deploy from current directory

1
2
3
4
gcloud run deploy \
  --project $GCP_PROJECT_NAME \
  --source . \
  --region europe-west1

Advanced deploy with environment variables

1
2
3
4
5
6
7
8
gcloud run deploy goapp \
  --project $GCP_PROJECT_NAME \
  --region europe-west1 \
  --image gcr.io/$GCP_PROJECT_NAME/goapp:v0.1.2 \
  --platform managed \
  --port 80 \
  --allow-unauthenticated \
  --set-env-vars DB_USER=root,DB_PSWD='SecretPss123@',DB_NAME=citizix,DB_HOST="192.168.111.4"

On success you will see output similar to this:

1
2
3
4
5
6
7
8
Deploying container to Cloud Run service [goapp] in project [citizix-run-project] region [europe-west1]
✓ Deploying... Done.
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service [myapp] revision [myapp-00002-yay] has been deployed and is serving 100 percent of traffic.
Service URL: https://myapp-[hash].a.run.app

To confirm that the application is working as expected, initiate a request to the given URL:

1
2
$ curl https://myapp-[hash].a.run.app
Hello world!

The above shows that it is working as expected.

Checking logs

1
gcloud beta run services logs tail goapp --project $GCP_PROJECT_NAME --region europe-west1

Inspecting Cloud run services

Now that the service is up and running, we can use these gcloud commands to manage them.

List cloud run services:

1
2
3
4
$ gcloud run services list --project $GCP_PROJECT_NAME

   SERVICE  REGION        URL                                    LAST DEPLOYED BY    LAST DEPLOYED AT
✔  goapp    europe-west1  https://myapp-2kn7s444fa-ew.a.run.app  admin@citizix.com  2023-01-13T04:48:46.697844Z

Describe the service to get its details:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
$ gcloud run services describe myapp --project $GCP_PROJECT_NAME --region=europe-west1

✔ Service goapp in region europe-west1

URL:     https://myapp-2kn7s444fa-ew.a.run.app
Ingress: all
Traffic:
  100% LATEST (currently myapp-00002-yay)

Last updated on 2023-01-13T04:48:46.697844Z by eutiekip@gmail.com:
  Revision myapp-00002-yay
  Image:           gcr.io/dev-fiddle/goapp:0.1.1
  Port:            8080
  Memory:          512Mi
  CPU:             1000m
  Service account: 615659745710-compute@developer.gserviceaccount.com
  Concurrency:     80
  Max Instances:   2
  Timeout:         300s

If you no longer need the service you can delete using this command:

1
2
3
4
5
6
7
8
$ gcloud run services delete goapp --region europe-west1 --project $GCP_PROJECT_NAME

Service [goapp] will be deleted.

Do you want to continue (Y/n)?  y

Deleting [goapp]...done.
Deleted service [goapp].

Conclusion

Google Cloud Run is specifically optimized for request-driven HTTP/HTTPS workloads. The containers can be preempted and migrated, so it is not great if you are planning to use it for long running services. There is no support other than HTTP/HTTPS for now either.

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy