快速入门:构建和部署

本页面介绍如何创建简单的 Hello World 应用,将该应用打包到容器映像中,然后将该映像上传到 Container Registry 并部署到 Cloud Run。 示例以多种语言提供,但请注意,除了所提供的语言以外,您也可以使用其他语言。

您可以通过 Cloud Shell 以互动教程方式使用本教程:

启动互动教程

或者,您也可以使用 Qwiklabs 上的演示帐号,按照本快速入门进行操作。

准备工作

  1. 登录您的 Google 帐号。

    如果您还没有 Google 帐号,请注册新帐号

  2. 在 Google Cloud Console 的项目选择器页面上,选择或创建一个 Google Cloud 项目。

    转到项目选择器页面

  3. 确保您的 Cloud 项目已启用结算功能。 了解如何确认您的项目是否已启用结算功能

  4. 安装并初始化 Cloud SDK

编写示例应用

如需了解如何创建在 Cloud Run 上运行的示例 hello world 应用,请点击与您的语言对应的标签页:

Go

  1. 创建名为 helloworld 的新目录,并转到此目录中:

    mkdir helloworld
    cd helloworld
    
  2. 初始化 go.mod 文件以声明 go 模块

    module github.com/GoogleCloudPlatform/golang-samples/run/helloworld
    
    go 1.13
    
  3. 创建名为 main.go 的新文件,并将以下代码粘贴到其中:

    
    // Sample run-helloworld is a minimal Cloud Run service.
    package main
    
    import (
    	"fmt"
    	"log"
    	"net/http"
    	"os"
    )
    
    func main() {
    	log.Print("starting server...")
    	http.HandleFunc("/", handler)
    
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("defaulting to port %s", port)
    	}
    
    	// Start HTTP server.
    	log.Printf("listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    func handler(w http.ResponseWriter, r *http.Request) {
    	name := os.Getenv("NAME")
    	if name == "" {
    		name = "World"
    	}
    	fmt.Fprintf(w, "Hello %s!\n", name)
    }
    

    此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

Node.js

  1. 创建名为 helloworld 的新目录,并转到此目录中:

    mkdir helloworld
    cd helloworld
    
  2. 创建一个包含以下内容的 package.json 文件:

    {
      "name": "helloworld",
      "description": "Simple hello world sample in Node",
      "version": "1.0.0",
      "private": true,
      "main": "index.js",
      "scripts": {
        "start": "node index.js",
        "test": "mocha test/index.test.js --exit",
        "system-test": "NAME=Cloud mocha test/system.test.js --timeout=180000",
        "lint": "eslint '**/*.js'",
        "fix": "eslint --fix '**/*.js'"
      },
      "engines": {
        "node": ">= 12.0.0"
      },
      "author": "Google LLC",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.17.1"
      },
      "devDependencies": {
        "google-auth-library": "^6.1.3",
        "got": "^11.0.0",
        "mocha": "^8.0.0",
        "supertest": "^6.0.0"
      }
    }
    
  3. 在同一目录中,创建一个 index.js 文件,并将以下代码行复制到其中:

    const express = require('express');
    const app = express();
    
    app.get('/', (req, res) => {
      const name = process.env.NAME || 'World';
      res.send(`Hello ${name}!`);
    });
    
    const port = process.env.PORT || 8080;
    app.listen(port, () => {
      console.log(`helloworld: listening on port ${port}`);
    });

    此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

Python

  1. 创建名为 helloworld 的新目录,并转到此目录中:

    mkdir helloworld
    cd helloworld
    
  2. 创建名为 main.py 的文件,并将以下代码粘贴到其中:

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

    此代码使用我们的“Hello World”问候语响应请求。HTTP 处理由容器中的 Gunicorn Web 服务器进行。当直接调用以供本地使用时,此代码会创建一个基本 Web 服务器,该服务器侦听 PORT 环境变量定义的端口。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

Java

创建一个 Spring Boot 应用。

  1. 在 Console 中,使用 cURL 和 unzip 命令新建一个空 Web 项目:

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

    上述命令将创建一个 Spring Boot 项目。

    如需在 Microscof Windows 上使用上述 cURL 命令,您需要以下命令行之一,或者选择性地使用 Spring Initializr(预加载配置)生成项目:

  2. 如需更新 src/main/java/com/example/helloworld/HelloworldApplication.java 中的 HelloworldApplication 类,请添加 @RestController 以处理 / 映射,同时添加 @Value 字段以提供 NAME 环境变量:

    
    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("${NAME:World}")
      String name;
    
      @RestController
      class HelloworldController {
        @GetMapping("/")
        String hello() {
          return "Hello " + name + "!";
        }
      }
    
      public static void main(String[] args) {
        SpringApplication.run(HelloworldApplication.class, args);
      }
    }
  3. 将服务器端口设置成由 application.properties 中的 PORT 环境变量定义:

    server.port=${PORT:8080}

此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

如需使用其他框架将 Java 部署到 Cloud Run,请查看 SparkVert.x 版 Knative 示例。

C#

  1. 安装 .NET Core SDK 3.1。请注意,我们只需执行此操作,即可在下一步中创建新的 Web 项目。Dockerfile(稍后将进行介绍)会将所有依赖项加载到容器中。

  2. 在 Console 中,使用 dotnet 命令新建一个空 Web 项目:

    dotnet new web -o helloworld-csharp
    
  3. 转到 helloworld-csharp 目录。

  4. 更新 Program.cs 中的 CreateHostBuilder 定义,以侦听由 PORT 环境变量定义的端口:

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

    此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

  5. 创建名为 Startup.cs 的文件,并将以下代码粘贴到其中:

    using System;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.AspNetCore.Http;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    
    namespace helloworld_csharp
    {
        public class Startup
        {
            // This method gets called by the runtime. Use this method to add services to the container.
            // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
            public void ConfigureServices(IServiceCollection services)
            {
            }
    
            // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
            public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
            {
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                }
    
                app.UseRouting();
    
                app.UseEndpoints(endpoints =>
                {
                    endpoints.MapGet("/", async context =>
                    {
                        var target = Environment.GetEnvironmentVariable("TARGET") ?? "World";
                        await context.Response.WriteAsync($"Hello {target}!\n");
                    });
                });
            }
        }
    }

    此代码使用我们的“Hello World”问候语响应请求。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

C++

  1. 创建名为 helloworld-cpp 的新目录,并转到此目录中:

    mkdir helloworld-cpp
    cd helloworld-cpp
    
  2. 创建名为 CMakeLists.txt 的新文件,并将以下代码粘贴到其中:

    cmake_minimum_required(VERSION 3.10)
    
    # Define the project name and where to report bugs.
    set(PACKAGE_BUGREPORT "https://github.com/GoogleCloudPlatform/cpp-samples/issues")
    project(Docker-Run-C++ CXX C)
    
    # Configure the Compiler options, we will be using C++17 features.
    set(CMAKE_CXX_STANDARD 17)
    set(CMAKE_CXX_STANDARD_REQUIRED ON)
    
    find_package(Boost 1.66 REQUIRED COMPONENTS program_options)
    find_package(Threads)
    
    # When using static libraries the FindgRPC.cmake module does not define the
    # correct dependencies (OpenSSL::Crypto, c-cares, etc) for gRPC::grpc.
    # Explicitly listing these dependencies avoids the undefined symbols problems.
    add_executable(cloud_run_hello cloud_run_hello.cc)
    target_link_libraries(cloud_run_hello PRIVATE Boost::headers
                                                  Boost::program_options
        Threads::Threads)
    
    include(GNUInstallDirs)
    install(TARGETS cloud_run_hello RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
  3. 创建名为 cloud_run_hello.cpp 的新文件,并将以下代码粘贴到其中:

    #include <boost/asio/ip/tcp.hpp>
    #include <boost/asio/strand.hpp>
    #include <boost/beast/core.hpp>
    #include <boost/beast/http.hpp>
    #include <boost/beast/version.hpp>
    #include <boost/program_options.hpp>
    #include <cstdlib>
    #include <iostream>
    #include <optional>
    #include <thread>
    
    namespace be = boost::beast;
    namespace asio = boost::asio;
    namespace po = boost::program_options;
    using tcp = boost::asio::ip::tcp;
    
    po::variables_map parse_args(int& argc, char* argv[]) {
      // Initialize the default port with the value from the "PORT" environment
      // variable or with 8080.
      auto port = [&]() -> std::uint16_t {
        auto env = std::getenv("PORT");
        if (env == nullptr) return 8080;
        auto value = std::stoi(env);
        if (value < std::numeric_limits<std::uint16_t>::min() ||
            value > std::numeric_limits<std::uint16_t>::max()) {
          std::ostringstream os;
          os << "The PORT environment variable value (" << value
             << ") is out of range.";
          throw std::invalid_argument(std::move(os).str());
        }
        return static_cast<std::uint16_t>(value);
      }();
    
      // Parse the command-line options.
      po::options_description desc("Server configuration");
      desc.add_options()
          //
          ("help", "produce help message")
          //
          ("address", po::value<std::string>()->default_value("0.0.0.0"),
           "set listening address")
          //
          ("port", po::value<std::uint16_t>()->default_value(port),
           "set listening port");
    
      po::variables_map vm;
      po::store(po::parse_command_line(argc, argv, desc), vm);
      po::notify(vm);
      if (vm.count("help")) {
        std::cout << desc << "\n";
      }
      return vm;
    }
    
    int main(int argc, char* argv[]) try {
      po::variables_map vm = parse_args(argc, argv);
    
      if (vm.count("help")) return 0;
    
      auto address = asio::ip::make_address(vm["address"].as<std::string>());
      auto port = vm["port"].as<std::uint16_t>();
      std::cout << "Listening on " << address << ":" << port << std::endl;
    
      auto handle_session = [](tcp::socket socket) {
        auto report_error = [](be::error_code ec, char const* what) {
          std::cerr << what << ": " << ec.message() << "\n";
        };
    
        be::error_code ec;
        for (;;) {
          be::flat_buffer buffer;
    
          // Read a request
          be::http::request<be::http::string_body> request;
          be::http::read(socket, buffer, request, ec);
          if (ec == be::http::error::end_of_stream) break;
          if (ec) return report_error(ec, "read");
    
          // Send the response
          // Respond to any request with a "Hello World" message.
          be::http::response<be::http::string_body> response{be::http::status::ok,
                                                             request.version()};
          response.set(be::http::field::server, BOOST_BEAST_VERSION_STRING);
          response.set(be::http::field::content_type, "text/plain");
          response.keep_alive(request.keep_alive());
          std::string greeting = "Hello ";
          auto const* target = std::getenv("TARGET");
          greeting += target == nullptr ? "World" : target;
          greeting += "\n";
          response.body() = std::move(greeting);
          response.prepare_payload();
          be::http::write(socket, response, ec);
          if (ec) return report_error(ec, "write");
        }
        socket.shutdown(tcp::socket::shutdown_send, ec);
      };
    
      asio::io_context ioc{/*concurrency_hint=*/1};
      tcp::acceptor acceptor{ioc, {address, port}};
      for (;;) {
        auto socket = acceptor.accept(ioc);
        if (!socket.is_open()) break;
        // Run a thread per-session, transferring ownership of the socket
        std::thread{handle_session, std::move(socket)}.detach();
      }
    
      return 0;
    } catch (std::exception const& ex) {
      std::cerr << "Standard exception caught " << ex.what() << '\n';
      return 1;
    }

    此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

PHP

  1. 创建名为 helloworld-php 的新目录,并转到此目录中:

    mkdir helloworld-php
    cd helloworld-php
    
  2. 创建名为 index.php 的文件,并将以下代码粘贴到其中:

    
    $name = getenv('NAME', true) ?: 'World';
    echo sprintf('Hello %s!', $name);
    

    此代码使用我们的“Hello World”问候语响应请求。HTTP 处理由容器中的 Apache Web 服务器进行。

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

Ruby

  1. 创建名为 helloworld 的新目录,并转到此目录中:

    mkdir helloworld
    cd helloworld
    
  2. 创建名为 app.rb 的文件,并将以下代码粘贴到其中:

    require "sinatra"
    
    set :bind, "0.0.0.0"
    port = ENV["PORT"] || "8080"
    set :port, port
    
    get "/" do
      name = ENV["NAME"] || "World"
      "Hello #{name}!"
    end

    此代码会创建一个基本 Web 服务器,以侦听由 PORT 环境变量定义的端口。

  3. 创建文件名 Gemfile,并将以下内容复制到其中:

    source "https://rubygems.org"
    
    gem "sinatra", "~>2.0"
    
    group :test do
      gem "rack-test"
      gem "rest-client"
      gem "rspec"
      gem "rspec_junit_formatter"
      gem "rubysl-securerandom"
    end
    
  4. 如果您未安装 Bundler 2.0 或更高版本,请安装 Bundler

  5. 通过运行以下命令生成 Gemfile.lock 文件:

    bundle install

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

Shell

  1. 创建名为 helloworld-shell 的新目录,并转到此目录中:

    mkdir helloworld-shell
    cd helloworld-shell
    
  2. 创建一个包含以下内容的 script.sh 文件:

    
    set -e
    echo "Hello ${NAME:-World}!"
    

    为了对每个传入请求执行此 shell 脚本,本示例使用一个小型 Go 程序,该程序会启动一个基本 Web 服务器并侦听 PORT 环境变量定义的端口。

  3. 创建一个包含以下内容的 invoke.go 文件:

    
    // Sample helloworld-shell is a Cloud Run shell-script-as-a-service.
    package main
    
    import (
    	"log"
    	"net/http"
    	"os"
    	"os/exec"
    )
    
    func main() {
    	http.HandleFunc("/", scriptHandler)
    
    	// Determine port for HTTP service.
    	port := os.Getenv("PORT")
    	if port == "" {
    		port = "8080"
    		log.Printf("Defaulting to port %s", port)
    	}
    
    	// Start HTTP server.
    	log.Printf("Listening on port %s", port)
    	if err := http.ListenAndServe(":"+port, nil); err != nil {
    		log.Fatal(err)
    	}
    }
    
    func scriptHandler(w http.ResponseWriter, r *http.Request) {
    	cmd := exec.CommandContext(r.Context(), "/bin/sh", "script.sh")
    	cmd.Stderr = os.Stderr
    	out, err := cmd.Output()
    	if err != nil {
    		w.WriteHeader(500)
    	}
    	w.Write(out)
    }
    

您的应用已编写完毕,可以进行容器化并上传到 Container Registry。

其他

Cloud Run 支持大多数语言。如需查看以此表中未显示的语言提供的简单示例,请参阅以下部分:

但在所有这些示例中,请忽略并省略有关 service.yaml 和 Docker Hub 的资料,因为 Cloud Run 不使用这些内容。

将应用容器化并将其上传到 Container Registry

如需将示例应用容器化,请在与源文件相同的目录中创建一个名为 Dockerfile 的新文件,并将以下内容复制到此文件中:

Go


# Use the offical golang image to create a binary.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.15-buster as builder

# Create and change to the app directory.
WORKDIR /app

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY . ./

# Build the binary.
RUN go build -mod=readonly -v -o server

# Use the official Debian slim image for a lean production container.
# https://hub.docker.com/_/debian
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM debian:buster-slim
RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /app/server

# Run the web service on container startup.
CMD ["/app/server"]

添加一个 .dockerignore 文件,以从容器映像中排除文件。

# The .dockerignore file excludes files from the container build process.
#
# https://docs.docker.com/engine/reference/builder/#dockerignore-file

# Exclude locally vendored dependencies.
vendor/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.gitignore

Node.js


# Use the official lightweight Node.js 12 image.
# https://hub.docker.com/_/node
FROM node:12-slim

# 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 copying both package.json AND package-lock.json (when available).
# Copying this first prevents re-running npm install on every code change.
COPY package*.json ./

# Install production dependencies.
# If you add a package-lock.json, speed your build by switching to 'npm ci'.
# RUN npm ci --only=production
RUN npm install --only=production

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD [ "node", "index.js" ]

添加一个 .dockerignore 文件,以从容器映像中排除文件。

Dockerfile
.dockerignore
node_modules
npm-debug.log

Python

Python Dockerfile 会启动 Gunicorn Web 服务器,该服务器会对 PORT 环境变量定义的端口进行侦听:


# Use the official lightweight Python image.
# https://hub.docker.com/_/python
FROM python:3.9-slim

# Allow statements and log messages to immediately appear in the Knative logs
ENV PYTHONUNBUFFERED True

# 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 --timeout 0 main:app

添加一个 .dockerignore 文件,以从容器映像中排除文件。

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

Java


# Use the official maven/Java 8 image to create a build artifact.
# https://hub.docker.com/_/maven
FROM maven:3.6-jdk-11 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/openjdk11: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", "-jar", "/helloworld.jar"]

添加一个 .dockerignore 文件,以从容器映像中排除文件。

Dockerfile
.dockerignore
target/

C#

# Use Microsoft's official build .NET image.
# https://hub.docker.com/_/microsoft-dotnet-core-sdk/
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-alpine AS build
WORKDIR /app

# Install production dependencies.
# Copy csproj and restore as distinct layers.
COPY *.csproj ./
RUN dotnet restore

# Copy local code to the container image.
COPY . ./
WORKDIR /app

# Build a release artifact.
RUN dotnet publish -c Release -o out

# Use Microsoft's official runtime .NET image.
# https://hub.docker.com/_/microsoft-dotnet-core-aspnet/
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-alpine AS runtime
WORKDIR /app
COPY --from=build /app/out ./

# Run the web service on container startup.
ENTRYPOINT ["dotnet", "helloworld-csharp.dll"]

如需排除通过本地 dotnet 构建操作生成的文件,不将其上传到 Cloud Build,请在示例应用的源文件所在目录中添加 .gcloudignore 文件:

# The .gcloudignore file excludes file from upload to Cloud Build.
# If this file is deleted, gcloud will default to .gitignore.
#
# https://cloud.google.com/cloud-build/docs/speeding-up-builds#gcloudignore
# https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore

**/obj/
**/bin/

# Exclude git history and configuration.
.git/
.gitignore

如果这些行位于 .gitignore 文件中,您可以跳过此步骤,因为 .gitignore.gcloudignore 配置的默认来源。

将这些行复制到 .dockerignore 文件中,以使用 docker CLI 进行本地容器构建。

C++

C++ Dockerfile 会启动一个应用,以侦听由 PORT 环境变量定义的端口:

# We chose Alpine to build the image because it has good support for creating
# statically-linked, small programs.
ARG DISTRO_VERSION=edge
FROM alpine:${DISTRO_VERSION} AS base

# Create separate targets for each phase, this allows us to cache intermediate
# stages when using Google Cloud Build, and makes the final deployment stage
# small as it contains only what is needed.
FROM base AS devtools

# Install the typical development tools and some additions:
#   - ninja-build is a backend for CMake that often compiles faster than
#     CMake with GNU Make.
#   - Install the boost libraries.
RUN apk update && \
    apk add \
        boost-dev \
        boost-static \
        build-base \
        cmake \
        git \
        gcc \
        g++ \
        libc-dev \
        nghttp2-static \
        ninja \
        openssl-dev \
        openssl-libs-static \
        tar \
        zlib-static

# Copy the source code to /v/source and compile it.
FROM devtools AS build
COPY . /v/source
WORKDIR /v/source

# Run the CMake configuration step, setting the options to create
# a statically linked C++ program
RUN cmake -S/v/source -B/v/binary -GNinja \
    -DCMAKE_BUILD_TYPE=Release \
    -DBoost_USE_STATIC_LIBS=ON \
    -DCMAKE_EXE_LINKER_FLAGS=-static

# Compile the binary and strip it to reduce its size.
RUN cmake --build /v/binary
RUN strip /v/binary/cloud_run_hello

# Create the final deployment image, using `scratch` (the empty Docker image)
# as the starting point. Effectively we create an image that only contains
# our program.
FROM scratch AS cloud-run-hello
WORKDIR /r

# Copy the program from the previously created stage and make it the entry point.
COPY --from=build /v/binary/cloud_run_hello /r

ENTRYPOINT [ "/r/cloud_run_hello" ]

PHP

PHP Dockerfile 会启动一个 Apache Web 服务器,该服务器会侦听 PORT 环境变量定义的端口:


# Use the official PHP image.
# https://hub.docker.com/_/php
FROM php:7.4-apache

# Configure PHP for Cloud Run.
# Precompile PHP code with opcache.
RUN docker-php-ext-install -j "$(nproc)" opcache
RUN set -ex; \
  { \
    echo "; Cloud Run enforces memory & timeouts"; \
    echo "memory_limit = -1"; \
    echo "max_execution_time = 0"; \
    echo "; File upload at Cloud Run network limit"; \
    echo "upload_max_filesize = 32M"; \
    echo "post_max_size = 32M"; \
    echo "; Configure Opcache for Containers"; \
    echo "opcache.enable = On"; \
    echo "opcache.validate_timestamps = Off"; \
    echo "; Configure Opcache Memory (Application-specific)"; \
    echo "opcache.memory_consumption = 32"; \
  } > "$PHP_INI_DIR/conf.d/cloud-run.ini"

# Copy in custom code from the host machine.
WORKDIR /var/www/html
COPY . ./

# Use the PORT environment variable in Apache configuration files.
# https://cloud.google.com/run/docs/reference/container-contract#port
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://github.com/docker-library/docs/blob/master/php/README.md#configuration
RUN mv "$PHP_INI_DIR/php.ini-development" "$PHP_INI_DIR/php.ini"

添加一个 .dockerignore 文件,以从容器映像中排除文件。

# The .dockerignore file excludes files from the container build process.
#
# https://docs.docker.com/engine/reference/builder/#dockerignore-file

# Exclude locally vendored dependencies.
vendor/

# Exclude "build-time" ignore files.
.dockerignore
.gcloudignore

# Exclude git history and configuration.
.gitignore

Ruby


# Use the official lightweight Ruby image.
# https://hub.docker.com/_/ruby
FROM ruby:2.7-slim

# Install production dependencies.
WORKDIR /usr/src/app
COPY Gemfile Gemfile.lock ./
ENV BUNDLE_FROZEN=true
RUN gem install bundler && bundle install --without test

# Copy local code to the container image.
COPY . ./

# Run the web service on container startup.
CMD ["ruby", "./app.rb"]

添加一个 .dockerignore 文件,以从容器映像中排除文件。

Dockerfile
README.md
.ruby-version
.bundle/
vendor/

Shell


# Use the offical golang image to create a binary.
# This is based on Debian and sets the GOPATH to /go.
# https://hub.docker.com/_/golang
FROM golang:1.14-buster as builder

# Create and change to the app directory.
WORKDIR /app

# Retrieve application dependencies.
# This allows the container build to reuse cached dependencies.
# Expecting to copy go.mod and if present go.sum.
COPY go.* ./
RUN go mod download

# Copy local code to the container image.
COPY invoke.go ./

# Build the binary.
RUN go build -mod=readonly -v -o server

# Use the official Debian slim image for a lean production container.
# https://hub.docker.com/_/debian
# https://docs.docker.com/develop/develop-images/multistage-build/#use-multi-stage-builds
FROM debian:buster-slim
RUN set -x && apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
    --no-install-recommends \
    ca-certificates && \
    rm -rf /var/lib/apt/lists/*

# Copy the binary to the production image from the builder stage.
COPY --from=builder /app/server /app/server
COPY script.sh ./

# Run the web service on container startup.
CMD ["/app/server"]

其他

Cloud Run 支持大多数语言。如需查看以此表中未显示的语言提供的示例 Dockerfile,请参阅以下部分:

但在这些示例中,请忽略并省略有关 service.yaml 和 Docker Hub 的资料,因为 Cloud Run 不使用这些内容。

使用 Cloud Build 构建容器映像,方法是从包含 Dockerfile 的目录中运行以下命令:

gcloud builds submit --tag gcr.io/PROJECT-ID/helloworld

其中 PROJECT-ID 是您的 GCP 项目 ID。 您可以通过运行 gcloud config get-value project 获取该 ID。

成功后,您会看到一条包含映像名称 (gcr.io/PROJECT-ID/helloworld) 的 SUCCESS 消息。该映像存储在 Container Registry 中,并可根据需要重复使用。

部署到 Cloud Run

要部署容器映像,请执行以下操作:

  1. 使用以下命令进行部署:

    gcloud run deploy --image gcr.io/PROJECT-ID/helloworld --platform managed

    PROJECT-ID 替换为您的 GCP 项目 ID。您可以通过运行 gcloud config get-value project 命令来查看项目 ID。

    1. 系统会提示您输入服务名称:按 Enter 接受默认名称 helloworld
    2. 系统会提示您输入区域:选择所需的区域,例如 us-central1
    3. 系统会提示您允许未通过身份验证的调用:响应 y

    然后等待部署完成。成功完成时,命令行会显示服务网址。

  2. 通过在网络浏览器中打开该服务网址来访问已部署的容器。

Cloud Run 位置

Cloud Run 是地区级的,这意味着运行 Cloud Run 服务的基础架构位于特定地区,并且由 Google 代管,以便在该地区内的所有区域以冗余方式提供。

选择用于运行 Cloud Run 服务的地区时,主要考虑该地区能否满足您的延迟时间、可用性或耐用性要求。通常,您可以选择距离用户最近的地区,但除此之外,您还应该考虑 Cloud Run 服务使用的其他 Google Cloud 产品的位置。跨多个位置使用 Google Cloud 产品可能会影响服务的延迟时间和费用。

Cloud Run 可在以下地区使用:

基于层级 1 价格

  • asia-east1(台湾)
  • asia-northeast1(东京)
  • asia-northeast2(大阪)
  • europe-north1(芬兰)
  • europe-west1(比利时)
  • europe-west4(荷兰)
  • us-central1(爱荷华)
  • us-east1(南卡罗来纳)
  • us-east4(北弗吉尼亚)
  • us-west1(俄勒冈)

基于层级 2 价格

  • asia-east2(香港)
  • asia-northeast3(韩国首尔)
  • asia-southeast1(新加坡)
  • asia-southeast2 (雅加达)
  • asia-south1(印度孟买)
  • australia-southeast1(悉尼)
  • europe-west2(英国伦敦)
  • europe-west3(德国法兰克福)
  • europe-west6(瑞士苏黎世)
  • northamerica-northeast1(蒙特利尔)
  • southamerica-east1(巴西圣保罗)

如果您已创建 Cloud Run 服务,则可以在 Cloud Console 的 Cloud Run 信息中心查看相应的地区。

恭喜!您刚刚将容器映像中打包的应用部署到了 Cloud Run。Cloud Run 会在需要处理收到的请求时自动横向扩容您的容器映像,并在需要处理的请求数量减少时自动横向缩容您的容器映像。您只需为在请求处理期间消耗的 CPU、内存和网络流量付费。

清理

移除测试项目

虽然 Cloud Run 不会对未在使用中的服务计费,但您可能仍然需要支付将容器映像存储在 Container Registry 中而产生的相关费用。 为避免产生费用,您可以删除映像或删除 Cloud 项目。删除 Cloud 项目后,系统即会停止对该项目中使用的所有资源计费。

  1. 在 Cloud Console 中,转到管理资源页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击关闭以删除项目。

后续步骤

如需详细了解如何使用代码源构建容器并推送到 Container Registry,请参阅以下内容: