Quickstart: Deploying a language-specific app

This page shows you how to do the following:

  1. Create a Hello World app.
  2. Package the app into a container image using Cloud Build.
  3. Create a cluster in Google Kubernetes Engine (GKE).
  4. Deploy the container image to your cluster.

The sample is shown in several languages, but note that you can use other languages in addition to the ones shown.

Before you begin

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. Select or create a GCP project.

    Go to the Project selector page

  3. Make sure that billing is enabled for your Google Cloud Platform project.

    Learn how to enable billing

  4. Enable the Cloud Build and Google Kubernetes Engine APIs.

    Enable the APIs

  5. Install and initialize the Cloud SDK.
  6. kubectl is used to manage Kubernetes, the cluster orchestration system used by GKE. You can install kubectl by using gcloud:
    gcloud components install kubectl

Writing the sample app

For instructions on creating a Hello World app that runs on GKE, click your language:

Go

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a new module named example.com/helloworld:

    go mod init example.com/helloworld
    
  3. Create a new file named helloworld.go and paste the following code into it:

    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	http.HandleFunc("/", handler)
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    	}
    	log.Printf("Listening on localhost:%s", port)
    	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", port), nil))
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	log.Print("Hello world received a request.")
    	target := os.Getenv("TARGET")
    	if target == "" {
    		target = "World"
    	}
    	fmt.Fprintf(w, "Hello %s!\n", target)
    }
    

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Container Registry.

Node.js

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a package.json file with the following contents:

    {
      "name": "gke-helloworld",
      "version": "1.0.0",
      "description": "GKE hello world sample in Node",
      "main": "index.js",
      "scripts": {
        "start": "node index.js"
      },
      "author": "",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.16.4"
      }
    }
    
  3. In the same directory, create a index.js file, and copy the following lines into it:

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      console.log('Hello world received a request.');
    
      const target = process.env.TARGET || 'World';
      res.send(`Hello ${target}!`);
    });
    
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log('Hello world listening on port', port);
    });
    

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container and uploaded to Container Registry.

Python

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a file named app.py and paste the following code into it:

    import os
    
    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def hello_world():
        target = os.environ.get('TARGET', 'World')
        return 'Hello {}!\n'.format(target)
    
    if __name__ == "__main__":
        app.run(debug=True,host='0.0.0.0',port=int(os.environ.get('PORT', 8080)))
    

Java

Create a Spring Boot app.

  1. Install Java SE 8 or higher JDK and cURL. Java SE and cURL are only required to create the new web project in the next step. The Dockerfile, which is described later, loads all dependencies into the container.

  2. From your terminal, create a new empty web project:

    curl https://start.spring.io/starter.zip \
        -d dependencies=web \
        -d javaVersion=1.8 \
        -d bootVersion=2.1.3.RELEASE \
        -d name=helloworld \
        -d artifactId=helloworld \
        -d baseDir=helloworld-gke \
        -o helloworld-gke.zip
    unzip helloworld-gke.zip
    cd helloworld-gke
    

    You now have a new Spring Boot project in helloworld-gke.

  3. In the src/main/java/com/example/helloworld/HelloworldApplication.java file, Update the HelloworldApplication class by adding a @RestController to handle the / mapping.

    package com.example.helloworld;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @SpringBootApplication
    public class HelloworldApplication {
    
    	@Value("${TARGET:World}")
    	String target;
    
    	@RestController
    	class HelloworldController {
    		@GetMapping("/")
    		String hello() {
    			return "Hello " + target + "!";
    		}
    	}
    
    	public static void main(String[] args) {
    		SpringApplication.run(HelloworldApplication.class, args);
    	}
    }
    

    This code creates a web server that listens on the port defined by the PORT environment variable.

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Container Registry.

C#

  1. Install .NET Core SDK 2.2. .NET Core SDK is only required to create the new web project in the next step. The Dockerfile, which is described later, loads all dependencies into the container.

  2. From your terminal, create a new empty web project:

    dotnet new web -o helloworld-gke
    
  3. Change directory to helloworld-gke.

  4. Update the CreateWebHostBuilder definition in Program.cs by specifying the port URL for .UseUrls() to define the serving port. The sample shows port 8080, but you can use other ports. Your server must listen to whatever port you specify here:

    using System;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    
    namespace helloworld_csharp
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
            public static IWebHostBuilder CreateWebHostBuilder(string[] args)
            {
                string port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
                string url = String.Concat("http://0.0.0.0:", port);
    
                return WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>().UseUrls(url);
            }
        }
    }
    

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Container Registry.

PHP

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a file named index.php and paste the following code into it:

    <?php
    $target = getenv('TARGET', true) ?: 'World';
    echo sprintf("Hello %s!", $target);
    

Your app is finished and ready to be packaged in a Docker container, and then uploaded to Container Registry.

Ruby

  1. Create a new directory named helloworld-gke and change directory into it:

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. Create a file named app.rb and paste the following code into it:

    require 'sinatra'
    
    set :bind, '0.0.0.0'
    set :port, ENV['PORT'] || '8080'
    
    get '/' do
      target = ENV['TARGET'] || 'World'
      "Hello #{target}!\n"
    end
    

    This code creates a web server that listens on the port defined by the PORT environment variable.

  3. Create a file named Gemfile and copy the following into it:

    source 'https://rubygems.org'
    
    gem 'sinatra'
    gem 'rack', '>= 2.0.6'
    

Containerizing an app with Cloud Build

  1. To containerize the sample app, create a new file named Dockerfile in the same directory as the source files, and copy the following content:

    Go

    # Use the offical Golang image to create a build artifact.
    # This is based on Debian and sets the GOPATH to /go.
    # https://hub.docker.com/_/golang
    FROM golang:1.12 as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY . .
    
    # Build the command inside the container.
    RUN CGO_ENABLED=0 GOOS=linux go build -v -o helloworld
    
    # Use a Docker multi-stage build to create a lean production image.
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM alpine
    RUN apk add --no-cache ca-certificates
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/helloworld /helloworld
    
    # Run the web service on container startup.
    CMD ["/helloworld"]
    

    Node.js

    # Use the official Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:10
    
    # Create and change to the app directory.
    WORKDIR /usr/src/app
    
    # Copy application dependency manifests to the container image.
    # A wildcard is used to ensure both package.json AND package-lock.json are copied.
    # Copying this separately prevents re-running npm install on every code change.
    COPY package*.json ./
    
    # Install production dependencies.
    RUN npm install --only=production
    
    # Copy local code to the container image.
    COPY . .
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]
    

    Add a further .dockerignore file to ensure local files do not affect the container build process:

    Dockerfile
    README.md
    node_modules
    npm-debug.log
    

    Python

    # Use the official Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7
    
    # Copy local code to the container image.
    ENV APP_HOME /app
    WORKDIR $APP_HOME
    COPY . .
    
    # Install production dependencies.
    RUN pip install Flask gunicorn
    
    # Run the web service on container startup. Here we use the gunicorn
    # webserver, with one worker process and 8 threads.
    # For environments with multiple CPU cores, increase the number of workers
    # to be equal to the cores available.
    CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 app:app
    

    Add a .dockerignore file to ensure local files don't affect the container build process:

    Dockerfile
    README.md
    *.pyc
    *.pyo
    *.pyd
    __pycache__
    

    Java

    # Use the official maven/Java 8 image to create a build artifact.
    # https://hub.docker.com/_/maven
    FROM maven:3.5-jdk-8-alpine as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY pom.xml .
    COPY src ./src
    
    # Build a release artifact.
    RUN mvn package -DskipTests
    
    # Use AdoptOpenJDK for base image.
    # It's important to use OpenJDK 8u191 or above that has container support enabled.
    # https://hub.docker.com/r/adoptopenjdk/openjdk8
    # https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
    FROM adoptopenjdk/openjdk8:jdk8u202-b08-alpine-slim
    
    # Copy the jar to the production image from the builder stage.
    COPY --from=builder /app/target/helloworld-*.jar /helloworld.jar
    
    # Run the web service on container startup.
    CMD ["java","-Djava.security.egd=file:/dev/./urandom","-Dserver.port=${PORT}","-jar","/helloworld.jar"]
    

    C#

    # Use Microsoft's official .NET image.
    # https://hub.docker.com/r/microsoft/dotnet
    FROM microsoft/dotnet:2.2-sdk
    
    # Install production dependencies.
    # Copy csproj and restore as distinct layers.
    WORKDIR /app
    COPY *.csproj .
    RUN dotnet restore
    
    # Copy local code to the container image.
    COPY . .
    
    # Build a release artifact.
    RUN dotnet publish -c Release -o out
    
    # Run the web service on container startup.
    CMD ["dotnet", "out/helloworld-csharp.dll"]
    

    Add a .dockerignore file to ensure local files don't affect the container build process:

    Dockerfile
    README.md
    **/obj/
    **/bin/
    

    PHP

    # Use the official PHP 7.2 image.
    # https://hub.docker.com/_/php
    FROM php:7.2-apache
    
    # Copy local code to the container image.
    COPY index.php /var/www/html/
    
    # Use port 8080 in Apache configuration files.
    RUN sed -i 's/80/${PORT}/g' /etc/apache2/sites-available/000-default.conf /etc/apache2/ports.conf
    
    # Configure PHP for development.
    # Switch to the production php.ini for production operations.
    # RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
    # https://hub.docker.com/_/php#configuration
    RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"
    

    Add a .dockerignore file to ensure local files don't affect the container build process:

    Dockerfile
    README.md
    vendor
    

    Ruby

    # Use the official Ruby image.
    # https://hub.docker.com/_/ruby
    FROM ruby:2.5
    
    # Install production dependencies.
    WORKDIR /usr/src/app
    COPY Gemfile ./
    RUN bundle install
    
    # Copy local code to the container image.
    COPY . .
    
    # Run the web service on container startup.
    CMD ["ruby", "./app.rb"]
    

  2. Get your GCP project ID:

    gcloud config get-value project
    

    Build your container image using Cloud Build, which is similar to running docker build and docker push, but it happens on Google Cloud Platform (GCP). Replace PROJECT_ID with your GCP ID:

    gcloud builds submit --tag gcr.io/PROJECT_ID/helloworld-gke .
    

    The image is stored in Container Registry.

Creating a Kubernetes Engine cluster

A GKE cluster is a managed set of Compute Engine virtual machines that operate as a single GKE cluster. This tutorial uses a single node.

  1. Create the cluster. Replace YOUR_GCP_ZONE with the GCP zone where you want to host your cluster. For a complete list, see Geography and regions.

    gcloud container clusters create helloworld-gke \
       --num-nodes 1 \
       --enable-basic-auth \
       --issue-client-certificate \
       --zone YOUR_GCP_ZONE
    
  2. Verify that you have access to the cluster. The following command lists the nodes in your container cluster which are up and running and indicates that you have access to it.

    kubectl get nodes
    

    If you run into errors, refer to the Kubernetes Troubleshooting guide

Deploying to GKE

To deploy your app to the GKE cluster you created, you need two Kubernetes objects.

  1. A Deployment to define your app.
  2. A Service to define how to access your app.

Deploy an app

The app has a frontend server that handles the web requests. You define the cluster resources needed to run the frontend in a new file called deployment.yaml. These resources are described as a Deployment. You use Deployments to create and update a ReplicaSet and its associated pods.

  1. Create the deployment.yaml file in the same directory as your other files and copy the following content, replacing $GCLOUD_PROJECT with your GCP project ID:

    # This file configures the hello-world app which serves public web traffic.
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: helloworld-gke
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello
      template:
        metadata:
          labels:
            app: hello
        spec:
          containers:
          - name: hello-app
            # Replace $GCLOUD_PROJECT with your project ID
            image: gcr.io/$GCLOUD_PROJECT/helloworld-gke:latest
            # This app listens on port 8080 for web traffic by default.
            ports:
            - containerPort: 8080
            env:
              - name: PORT
                value: "8080"
    
  2. Deploy the resource to the cluster:

    kubectl apply -f deployment.yaml
    
  3. Track the status of the Deployment:

    kubectl get deployments
    

    After the Deployment has the same number of AVAILABLE pods as DESIRED pods, the Deployment is complete.

    NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    hello-deployment   1         1         1            1           20s
    

    If the Deployment has a mistake, run kubectl apply -f deployment.yaml again to update the Deployment with any changes.

  4. After the Deployment is complete, you can see the pods that the Deployment created:

    kubectl get pods
    

Deploy a Service

Services provide a single point of access to a set of pods. While it's possible to access a single pod, pods are ephemeral and can only be accessed reliably by using a Service address. In your Hello World app, the "hello" Service defines a load balancer to access the hello-app pods from a single IP address. This Service is defined in the service.yaml file.

  1. Create the file service.yaml in the same directory as your other source files with the following content:

    # The hello service provides a load-balancing proxy over the hello-app
    # pods. By specifying the type as a 'LoadBalancer', Kubernetes Engine will
    # create an external HTTP load balancer.
    apiVersion: v1
    kind: Service
    metadata:
      name: hello
    spec:
      type: LoadBalancer
      selector:
        app: hello
      ports:
      - port: 80
        targetPort: 8080
    

    The pods are defined separately from the Service that uses the pods. Kubernetes uses labels to select the pods that a Service addresses. With labels, you can have a Service that addresses pods from different replica sets and have multiple Services that point to an individual pod.

  2. Create the Hello World Service:

    kubectl apply -f service.yaml
    
  3. Get the Service's external IP address:

    kubectl get services
    

    It can take up to 60 seconds to allocate the IP address. The external IP address is listed under the column EXTERNAL-IP for the hello Service.

    NAME         TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
    hello        LoadBalancer   10.22.222.222   35.111.111.11   80:32341/TCP   1m
    kubernetes   ClusterIP      10.22.222.1     <none>          443/TCP        20m
    

View a deployed app

You have now deployed all the resources needed to run the Hello World app on GKE. Use the external IP address from the previous step to load the app in your web browser, and see your running app!

# Example cURL call to your running appication on GKE
$ kubectl get service hello \
    -o=custom-columns=NAME:.status.loadBalancer.ingress[*].ip --no-headers
35.111.111.11
$ curl 35.111.111.11
Hello World!

Clean up

To avoid incurring charges to your GCP account for the resources used in this quickstart:

You are charged for the Compute Engine instances running in your cluster, as well as for the container image in Container Registry.

Delete the project

Deleting your GCP project stops billing for all the resources used within that project.

  1. In the GCP Console, go to the Projects page.

    Go to the Projects page

  2. In the project list, select the project you want to delete and click Delete .
  3. In the dialog, type the project ID, and then click Shut down to delete the project.

Delete your cluster and container

If you want to keep your project but only delete the resources used in this tutorial, delete your cluster and image.

To delete a cluster using the gcloud command-line tool, run the following command:

gcloud container clusters delete helloworld-gke

To delete an image from one of your Container Registry repositories, run the following command:

gcloud container images delete gcr.io/[PROJECT-ID]/helloworld-gke

What's next

For more information on Kubernetes, see the following:

For more information on deploying to GKE, see the following:

For more information on deploying to GKE directly from your IDE with Cloud Code, see the following:

Var denne siden nyttig? Si fra hva du synes:

Send tilbakemelding om ...

Kubernetes Engine Documentation