コンテナ イメージ内のアプリを GKE クラスタにデプロイする


このページでは、次の方法について説明します。

  1. Hello World アプリを作成する。
  2. Cloud Build を使用して、アプリをコンテナ イメージにパッケージ化する。
  3. Google Kubernetes Engine(GKE)でクラスタを作成する。
  4. コンテナ イメージをクラスタにデプロイする。

ここでは、複数の言語でサンプルを示しますが、その他の言語を使用することもできます。


このタスクを Cloud Shell エディタで直接行う際のガイダンスについては、「ガイドを表示」をクリックしてください。

ガイドを表示


始める前に

  1. Google Cloud アカウントにログインします。Google Cloud を初めて使用する場合は、アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $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 は、GKE で使用されるクラスタ オーケストレーション システムである Kubernetes の管理に使用されます。kubectlgcloud でインストールできます。
    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)
    }
    

    このコードは、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);
    });

    このコードは、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 は、次のステップで新しいウェブ プロジェクトを作成する場合にのみ必要になります。Dockerfile がすべての依存関係をコンテナに読み込みます(詳細はあとで説明します)。

  2. ターミナルで、新しい空のウェブ プロジェクトを作成します。

    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
    

    新しい Spring Boot プロジェクトが helloworld-gke に作成されました。

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

    このコードは、PORT 環境変数で定義されたポートをリッスンするウェブサーバーを作成します。

これでアプリが完成しました。このアプリを Docker コンテナにパッケージ化し、Artifact Registry にアップロードできます。

C#

  1. .NET SDK をインストールします。.NET SDK は、次のステップで新しいウェブ プロジェクトを作成する場合にのみ必要です。Dockerfile がすべての依存関係をコンテナに読み込みます(詳細はあとで説明します)。

  2. ターミナルで、新しい空のウェブ プロジェクトを作成します。

    dotnet new web -o helloworld-gke
    
  3. helloworld-gke ディレクトリに移動します。

    cd helloworld-gke
    
  4. ポート 8080 をリッスンするように Program.cs を更新します。

    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 クラスタにアプリをデプロイするには、2 つの Kubernetes オブジェクトが必要です。

  1. アプリを定義する Deployment
  2. アプリへのアクセス方法を定義する Service

アプリをデプロイする

アプリには、ウェブ リクエストを処理するフロントエンド サーバーがあります。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 Deployment が READY の場合、デプロイが完了します。

    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 は、ラベルを使用して Service と通信する Pod を選択します。ラベルを使用すると、1 つのサービスで異なるレプリカセットの Pod に対応することも、複数のサービスで 1 つずつの Pod に対応することもできます。

  2. Hello World Service を作成します。

    kubectl apply -f service.yaml
    
  3. Service の外部 IP アドレスを探します。

    kubectl get services
    

    IP アドレスの割り振りに 60 秒ほどかかることがあります。外部 IP アドレスが、hello Service の 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 インスタンスArtifact 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 上でアプリケーションを作成、開発、実行する方法を学習する。以下をご覧ください。