将容器映像中的应用部署到 GKE 集群


本页面介绍如何执行以下操作:

  1. 创建一个 Hello World 应用。
  2. 使用 Cloud Build 将该应用封装到容器映像中。
  3. 在 Google Kubernetes Engine (GKE) 中创建一个集群。
  4. 将容器映像部署到该集群。

示例以多种语言提供,但是,除了所提供的语言以外,您也可以使用其他语言。


如需在 Cloud Shell Editor 中直接遵循有关此任务的分步指导,请点击操作演示

操作演示


准备工作

  1. 登录您的 Google Cloud 账号。如果您是 Google Cloud 新手,请创建一个账号来评估我们的产品在实际场景中的表现。新客户还可获享 $300 赠金,用于运行、测试和部署工作负载。
  2. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  3. 确保您的 Google Cloud 项目已启用结算功能

  4. 启用 Artifact Registry, Cloud Build, and Google Kubernetes Engine API。

    启用 API

  5. 安装 Google Cloud CLI。
  6. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  7. 在 Google Cloud Console 中的项目选择器页面上,选择或创建一个 Google Cloud 项目

    转到“项目选择器”

  8. 确保您的 Google Cloud 项目已启用结算功能

  9. 启用 Artifact Registry, Cloud Build, and Google Kubernetes Engine API。

    启用 API

  10. 安装 Google Cloud CLI。
  11. 如需初始化 gcloud CLI,请运行以下命令:

    gcloud init
  12. kubectl 用于管理 Kubernetes(即 GKE 使用的集群编排系统)。您可以使用 gcloud 安装 kubectl
    gcloud components install kubectl

编写示例应用

如需查看如何创建在 GKE 上运行的 Hello World 应用的说明,请点击您想用的语言:

Go

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

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. 创建名为 example.com/helloworld 的新模块:

    go mod init example.com/helloworld
    
  3. 创建名为 helloworld.go 的新文件,并将以下代码粘贴到其中:

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

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

您的应用已编写完毕,接下来可以将其封装到一个 Docker 容器中,然后上传到 Artifact Registry。

Node.js

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

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

    {
      "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. 在同一目录中,创建一个 index.js 文件,并将以下行复制到此文件中:

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

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

您的应用已编写完毕,接下来可以将其封装到一个 Docker 容器中,然后上传到 Artifact Registry。

Python

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

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

    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

创建一个 Spring Boot 应用。

  1. 安装 Java SE 8 或更高版本的 JDKcURL。 Java SE 和 cURL 只在下一步中创建新的 Web 项目时需要。Dockerfile(将在稍后介绍)会将所有依赖项加载到容器中。

  2. 在您的终端新建一个空 Web 项目:

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

    您现在已经有了一个位于 helloworld-gke 中的新 Spring Boot 项目。

  3. src/main/java/com/example/helloworld/HelloworldApplication.java 文件中,添加 @RestController 来更新 HelloworldApplication 类,以处理 / 映射。

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

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

您的应用已编写完毕,接下来可以将其封装到一个 Docker 容器中,然后上传到 Artifact Registry。

C#

  1. 安装 .NET SDK。.NET SDK 只在下一步中创建新的 Web 项目时需要。Dockerfile(将在稍后介绍)会将所有依赖项加载到容器中。

  2. 在您的终端新建一个空 Web 项目:

    dotnet new web -o helloworld-gke
    
  3. 切换到 helloworld-gke 目录。

    cd helloworld-gke
    
  4. 更新 Program.cs 以监听端口 8080

    var builder = WebApplication.CreateBuilder(args);
    
    // Google Cloud Run sets the PORT environment variable to tell this
    // process which port to listen to.
    var port = Environment.GetEnvironmentVariable("PORT") ?? "8080";
    var url = $"http://0.0.0.0:{port}";
    var target = Environment.GetEnvironmentVariable("TARGET") ?? "World";
    
    var app = builder.Build();
    
    app.MapGet("/", () => $"Hello {target}!");
    
    app.Run(url);

您的应用已编写完毕,接下来可以将其封装到一个 Docker 容器中,然后上传到 Artifact Registry。

PHP

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

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

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

您的应用已编写完毕,接下来可以将其封装到一个 Docker 容器中,然后上传到 Artifact Registry。

使用 Cloud Build 将应用容器化

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

    Go

    # Use the offical Go 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.21.0 as builder
    WORKDIR /app
    
    # Initialize a new Go module.
    RUN go mod init quickstart-go
    
    # Copy local code to the container image.
    COPY *.go ./
    
    # Build the command inside the container.
    RUN CGO_ENABLED=0 GOOS=linux go build -o /quickstart-go
    
    # 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 gcr.io/distroless/base-debian11
    
    # Change the working directory.
    WORKDIR /
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /quickstart-go /quickstart-go
    
    # Run the web service on container startup.
    USER nonroot:nonroot
    ENTRYPOINT ["/quickstart-go"]

    Node.js

    # Use the official lightweight Node.js 16 image.
    # https://hub.docker.com/_/node
    FROM node:17-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 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 --omit=dev
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD [ "npm", "start" ]

    再添加一个 .dockerignore 文件,以确保本地文件不会影响容器的构建过程:

    Dockerfile
    README.md
    node_modules
    npm-debug.log
    

    Python

    # Use the official lightweight Python image.
    # https://hub.docker.com/_/python
    FROM python:3.7-slim
    
    # 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

    添加 .dockerignore 文件,以确保本地文件不会影响容器的构建过程:

    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 lightweight .NET images.
    FROM mcr.microsoft.com/dotnet/sdk:6.0 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 . ./
    
    # Build a release artifact.
    RUN dotnet publish -c Release -o out
    
    # Run the web service on container startup in a lean production image.
    FROM mcr.microsoft.com/dotnet/aspnet:6.0
    WORKDIR /app
    COPY --from=build /app/out .
    
    # Start the .dll (will have the same name as your .csproj file)
    ENTRYPOINT ["dotnet", "helloworld-gke.dll"]

    添加 .dockerignore 文件,以确保本地文件不会影响容器的构建过程:

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

    PHP

    # Use the official PHP 7.4 image.
    # https://hub.docker.com/_/php
    FROM php:7.4-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"

    添加 .dockerignore 文件,以确保本地文件不会影响容器的构建过程:

    Dockerfile
    README.md
    vendor
    
  2. 获取 Google Cloud 项目 ID:

    gcloud config get-value project
    
  3. 在本快速入门中,您需要将容器存储在 Artifact Registry 中,并从注册表中将其部署到集群。运行以下命令,在集群所在的位置创建名为 hello-repo 的仓库:

    gcloud artifacts repositories create hello-repo \
        --project=PROJECT_ID \
        --repository-format=docker \
        --location=us-central1 \
        --description="Docker repository"
    

    替换以下值:

    • PROJECT_ID 是您的 Google Cloud 项目 ID
  4. 使用 Cloud Build 构建容器映像,此行为类似于运行 docker builddocker push,但构建是在 Google Cloud 上进行的:

    gcloud builds submit \
      --tag us-central1-docker.pkg.dev/PROJECT_ID/hello-repo/helloworld-gke .
    

    该映像存储在 Artifact Registry 中。

创建 GKE 集群

GKE 集群是作为单个 GKE 集群运行的一组托管式 Compute Engine 虚拟机。

  1. 创建集群。

    gcloud container clusters create-auto helloworld-gke \
      --location us-central1
    
  2. 验证您有权访问该集群。以下命令会列出您的容器集群中已启动并运行的节点,并表明您有权访问该集群。

    kubectl get nodes
    

    如果您遇到了错误,请参阅 Kubernetes 问题排查指南

部署到 GKE

如需将该应用部署到您创建好的 GKE 集群,您需要用到两个 Kubernetes 对象。

  1. 一个 Deployment 对象,用来定义您的应用。
  2. 一个 Service 对象,用来定义如何访问您的应用。

部署应用

该应用具有一个前端服务器,用于处理 Web 请求。您可以在名为 deployment.yaml 的新文件中定义运行该前端所需的集群资源。这些资源通过一个 Deployment 对象来描述。您可以使用 Deployment 来创建和更新 ReplicaSet 以及与其关联的 Pod。

  1. 在与其他文件相同的目录中创建 deployment.yaml 文件,然后复制以下内容。在文件中替换以下值:

    • $GCLOUD_PROJECT 是您的 Google Cloud 项目 ID:
    • $LOCATION 是代码库位置,例如 us-central1
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: helloworld-gke
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello
      template:
        metadata:
          labels:
            app: hello
        spec:
          containers:
          - name: hello-app
            # Replace $LOCATION with your Artifact Registry location (e.g., us-west1).
            # Replace $GCLOUD_PROJECT with your project ID.
            image: $LOCATION-docker.pkg.dev/$GCLOUD_PROJECT/hello-repo/helloworld-gke:latest
            # This app listens on port 8080 for web traffic by default.
            ports:
            - containerPort: 8080
            env:
              - name: PORT
                value: "8080"
            resources:
              requests:
                memory: "1Gi"
                cpu: "500m"
                ephemeral-storage: "1Gi"
              limits:
                memory: "1Gi"
                cpu: "500m"
                ephemeral-storage: "1Gi"
  2. 将资源部署到集群:

    kubectl apply -f deployment.yaml
    
  3. 跟踪 Deployment 的状态:

    kubectl get deployments
    

    如果所有 AVAILABLE 部署都为 READY,则表示 Deployment 已完成。

    NAME              READY   UP-TO-DATE   AVAILABLE   AGE
    helloworld-gke    1/1     1            1           20s
    

    如果 Deployment 有误,请再次运行 kubectl apply -f deployment.yaml,更新 Deployment 以纳入任何更改。

  4. Deployment 完成后,您可以查看 Deployment 创建的 Pod:

    kubectl get pods
    

部署 Service

Service 提供对一组 Pod 的单一访问点。尽管您可以访问单个 Pod,但 Pod 是临时性的,只有使用一个 Service 地址才能进行可靠的访问。在您的 Hello World 应用中,名为“hello”的 Service 定义了一个负载均衡器,用于通过一个 IP 地址访问多个 hello-app Pod。此 Service 在 service.yaml 文件中定义。

  1. 请在与其他源文件相同的目录中创建一个名为 service.yaml 的文件,并在此文件中添加以下内容:

    # 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

    这些 Pod 与使用 Pod 的 Service 是分别定义的。Kubernetes 使用标签来选择服务指向的 Pod。利用标签,您既可以让一个 Service 指向来自不同副本集的多个 Pod,也可以让多个 Service 指向同一个 Pod。

  2. 创建 Hello World Service:

    kubectl apply -f service.yaml
    
  3. 获取服务的外部 IP 地址:

    kubectl get services
    

    分配 IP 地址最多可能需要 60 秒的时间。外部 IP 地址列在 hello Serivce 的 EXTERNAL-IP 列下。

    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
    

查看已部署的应用

现在,您已经部署好了在 GKE 上运行 Hello World 应用所需的全部资源。

使用在上一步中获取的外部 IP 地址在网络浏览器中加载应用,并查看正在运行的应用:

 http://EXTERNAL_IP

或者,您可以对 Service 的外部 IP 地址进行 curl 调用:

curl EXTERNAL_IP

输出显示以下内容:

Hello World!

清理

为避免因本页中使用的资源导致您的 Google Cloud 账号产生费用,请按照以下步骤操作。

您需要为集群中运行的 Compute Engine 实例Container Registry 中的容器映像付费。

删除项目

删除 Google Cloud 项目后,系统会停止对该项目中使用的所有资源计费。

  1. 在 Google Cloud 控制台中,进入管理资源页面。

    转到“管理资源”

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

删除您的集群和容器

如果您想保留项目,只删除本教程中使用的资源,请删除您的集群和映像。

如需使用 Google Cloud CLI 删除集群,请为您使用的模式运行以下命令:

gcloud container clusters delete helloworld-gke \
    --location us-central1

如需删除 Artifact Registry 代码库中的映像,请运行以下命令:

gcloud artifacts docker images delete \
    us-central1-docker.pkg.dev/PROJECT_ID/hello-repo/helloworld-gke

后续步骤

如需详细了解 Kubernetes,请参阅以下内容:

如需详细了解如何部署到 GKE,请参阅以下内容:

如需详细了解如何使用 Cloud Code 直接从您的 IDE 在 GKE 上创建、开发和运行应用,请参阅以下内容: