빠른 시작: 빌드 및 배포

이 페이지에서는 간단한 Hello World 애플리케이션을 만들고, 컨테이너 이미지에 패키지하고, 이미지를 Container Registry에 업로드한 다음, 컨테이너 이미지를 Cloud Run에 배포하는 방법을 보여줍니다. 샘플이 몇 가지 언어로 표시되지만, 여기에 표시된 언어 외에 다른 언어도 사용할 수 있습니다.

이 빠른 시작은 Cloud Shell을 사용하는 대화형 가이드 형식으로 제공됩니다.

대화형 가이드 실행

또는 Qwiklabs의 데모 계정으로 이 빠른 시작을 수행하세요.

시작하기 전에

  1. Google Cloud 계정에 로그인합니다. Google Cloud를 처음 사용하는 경우 계정을 만들고 Google 제품의 실제 성능을 평가해 보세요. 신규 고객에게는 워크로드를 실행, 테스트, 배포하는 데 사용할 수 있는 $300의 무료 크레딧이 제공됩니다.
  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
    

위와 같은 형식의 go.mod 파일을 직접 만들거나 다음을 사용하여 프로젝트 디렉터리에서 초기화할 수 있습니다.

    go mod init
  1. 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)
    }
    

    이 코드는 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"
      },
      "engines": {
        "node": ">= 12.0.0"
      },
      "author": "Google LLC",
      "license": "Apache-2.0",
      "dependencies": {
        "express": "^4.17.1"
      }
    }
    
  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}`);
    });

    이 코드는 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 웹 서버에서 수행됩니다. 로컬 사용을 위해 직접 호출되는 경우, 이 코드는 PORT 환경 변수로 정의된 포트를 리슨하는 기본 웹 서버를 만듭니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드할 준비가 되었습니다.

자바

Spring Boot 애플리케이션을 만듭니다.

  1. Console에서 cURL 및 unzip 명령어를 사용하여 새로운 빈 웹 프로젝트를 만듭니다.

    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
    

    이렇게 하면 SpringBoot 프로젝트가 생성됩니다.

    Microscoft Windows에서 위의 cURL 명령어를 사용하려면 다음 명령줄 중 하나가 필요합니다. 그렇지 않으면 선택적으로 Spring 초기화 메서드(미리 로드된 구성)를 사용하여 프로젝트를 생성해야 합니다.

  2. / 매핑을 처리하기 위해 @RestController를 추가하여 src/main/java/com/example/helloworld/HelloworldApplication.java에서 HelloworldApplication 클래스를 업데이트하고 @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}

이 코드는 PORT 환경 변수로 정의된 포트를 리슨하는 기본 웹 서버를 만듭니다.

앱이 완성되었으며 컨테이너화되어 Container Registry에 업로드할 준비가 되었습니다.

다른 프레임워크로 Cloud Run에 자바를 배포하려면 SparkVert.x의 Knative 샘플을 검토하세요.

C#

  1. .NET Core SDK 3.1을 설치합니다. 다음 단계에서 새 웹 프로젝트를 만들려면 다음 단계만 수행하면 됩니다. 나중에 설명할 Dockerfile은 모든 종속 항목을 컨테이너에 로드합니다.

  2. Console에서 dotnet 명령어를 사용하여 빈 웹 프로젝트를 새로 만듭니다.

    dotnet new web -o helloworld-csharp
    
  3. 디렉터리를 helloworld-csharp로 변경합니다.

  4. PORT 환경 변수환경 변수에 의해 정의된 포트를 리슨하는 Program.csCreateHostBuilder 정의를 업데이트합니다.

    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);
                    });
            }
        }
    }

    이 코드는 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(cpp-samples-cloud-run-hello-world 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)
    
    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;
    }

    이 코드는 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 웹 서버에서 수행됩니다.

앱이 완성되었으며 컨테이너화되어 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

    이 코드는 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}!"
    

    모든 새로 추가되는 요청에 이 셸 스크립트를 실행하기 위해 이 샘플에서는 기본 웹 서버를 시작하고 PORT 환경 변수로 정의된 포트를 리슨하는 작은 Go 프로그램이 사용됩니다.

  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은 대부분의 언어를 지원합니다. 이 표에 표시된 언어 이외의 다른 샘플은 다음을 참조하세요.

그러나 이러한 샘플에서 Cloud Run은 이러한 샘플을 사용하지 않으므로 service.yaml 및 Docker Hub에 대한 자료를 무시하고 생략합니다.

앱 컨테이너화 및 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.16-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은 PORT 환경 변수에 정의된 포트를 리슨하는 Gunicorn 웹 서버를 시작합니다.


# 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.
# Timeout is set to 0 to disable the timeouts of the workers to allow Cloud Run to handle instance scaling.
CMD exec gunicorn --bind :$PORT --workers 1 --threads 8 --timeout 0 main:app

.dockerignore 파일을 추가하여 컨테이너 이미지의 파일을 제외합니다.

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

자바


# 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 구성의 기본 소스이기 때문에 이 단계를 건너뛸 수 있습니다.

docker CLI를 사용해서 로컬 컨테이너 빌드에 대해 이 줄을 .dockerignore 파일에 복사합니다.

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은 PORT 환경 변수로 정의된 포트를 리슨하는 Apache 웹 서버를 시작합니다.


# 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.5-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은 대부분의 언어를 지원합니다. 이 표에 표시된 언어 이외의 다른 Dockerfiles 샘플은 다음을 참조하세요.

그러나 이러한 샘플에서 Cloud Run은 이러한 샘플을 사용하지 않으므로 service.yaml 및 Docker Hub에 대한 자료를 무시하고 생략합니다.

Dockerfile이 포함되어 있는 디렉터리에서 다음 명령어를 실행하여 Cloud Build로 컨테이너 이미지를 빌드합니다.

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

여기서 PROJECT-ID는 GCP 프로젝트 ID입니다. gcloud config get-value project를 실행하여 가져올 수 있습니다.

성공하면 이미지 이름(gcr.io/PROJECT-ID/helloworld)이 포함된 성공 메시지가 표시됩니다. 이미지는 Container Registry에 저장되며 원하는 경우 다시 사용할 수 있습니다.

Cloud Run에 배포

컨테이너 이미지를 배포하려면 다음 안내를 따르세요.

  1. 다음 명령어를 사용하여 배포합니다.

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

    API를 사용 설정하라는 메시지가 표시되면 y로 답하여 사용 설정합니다.

    PROJECT-ID를 GCP 프로젝트 ID로 바꿉니다. gcloud config get-value project 명령어를 실행하여 프로젝트 ID를 볼 수 있습니다.

    1. 서비스 이름을 입력하라는 메시지가 표시됩니다. 기본 이름 helloworld를 사용하려면 Enter 키를 누릅니다.
    2. 리전을 입력하라는 메시지가 표시되면 us-central1과 같은 리전을 선택합니다.
    3. 인증되지 않은 호출 허용 메시지가 표시되면 y로 응답합니다.

    그런 다음 배포가 완료될 때까지 잠시 기다립니다. 성공하면 명령줄에 서비스 URL이 표시됩니다.

  2. 웹브라우저에서 서비스 URL을 열고 배포한 컨테이너로 이동합니다.

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(브라질 상파울루)
  • us-west2(로스앤젤레스)
  • us-west3(라스베이거스)
  • us-west4(솔트레이크시티)

Cloud Run 서비스를 이미 만들었다면 Cloud Console의 Cloud Run 대시보드에서 리전을 확인할 수 있습니다.

수고하셨습니다. 컨테이너 이미지로 패키징된 애플리케이션을 Cloud Run에 배포했습니다. Cloud Run은 수신된 요청을 처리하기 위해 컨테이너 이미지를 자동 및 수평으로 확장한 다음 수요가 감소하면 수평 축소합니다. 요청 처리 도중 소비한 CPU, 메모리, 네트워킹에 대해서만 비용을 지불하면 됩니다.

삭제

테스트 프로젝트 삭제

Cloud Run에서는 서비스를 사용하지 않을 때 비용이 청구되지 않지만 Container Registry에 컨테이너 이미지를 저장하는 데 요금이 부과될 수 있습니다. 비용이 청구되지 않도록 이미지를 삭제하거나 Cloud 프로젝트를 삭제할 수 있습니다. 클라우드 프로젝트를 삭제하면 해당 프로젝트 내에서 사용되는 모든 리소스에 대한 청구가 중단됩니다.

  1. Cloud Console에서 리소스 관리 페이지로 이동합니다.

    리소스 관리로 이동

  2. 프로젝트 목록에서 삭제할 프로젝트를 선택하고 삭제를 클릭합니다.
  3. 대화상자에서 프로젝트 ID를 입력한 후 종료를 클릭하여 프로젝트를 삭제합니다.

다음 단계

코드 소스로부터 컨테이너를 빌드하고 Container Registry에 푸시하는 방법을 보려면 다음을 참조하세요.