컨테이너 이미지의 앱을 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. API Artifact Registry, Cloud Build, and Google Kubernetes Engine 사용 설정

    API 사용 설정

  5. Google Cloud CLI를 설치합니다.
  6. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

    gcloud init
  7. Google Cloud Console의 프로젝트 선택기 페이지에서 Google Cloud 프로젝트를 선택하거나 만듭니다.

    프로젝트 선택기로 이동

  8. Google Cloud 프로젝트에 결제가 사용 설정되어 있는지 확인합니다.

  9. API Artifact Registry, Cloud Build, and Google Kubernetes Engine 사용 설정

    API 사용 설정

  10. Google Cloud CLI를 설치합니다.
  11. gcloud CLI를 초기화하려면 다음 명령어를 실행합니다.

    gcloud init
  12. kubectl은 GKE에서 사용하는 클러스터 조정 시스템인 Kubernetes를 관리하는 데 사용됩니다. 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)
    }
    

    이 코드는 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)))

자바

Spring Boot 앱을 만듭니다.

  1. 자바 SE 8 이상 JDKcURL을 설치합니다. 자바 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
    

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

    이 코드는 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를 사용하여 컨테이너 이미지를 빌드합니다. 이는 Google Cloud에서 빌드된다는 점을 제외하고 docker builddocker push를 실행하는 것과 유사합니다.

    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. 앱을 정의하는 배포
  2. 앱에 액세스하는 방법을 정의하는 서비스

앱 배포

앱에는 웹 요청을 처리하는 프런트엔드 서버가 있습니다. deployment.yaml이라는 새 파일에서 프런트엔드를 실행하는 데 필요한 클러스터 리소스를 정의합니다. 이러한 리소스는 배포로 설명됩니다. 배포를 통해 ReplicaSet와 관련 포드를 만들고 업데이트합니다.

  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. 배포 상태를 추적합니다.

    kubectl get deployments
    

    모든 AVAILABLE 배포가 READY 상태가 되면 배포가 완료됩니다.

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

    배포에 문제가 있으면 kubectl apply -f deployment.yaml을 다시 실행하여 변경사항으로 배포를 업데이트합니다.

  4. 배포가 완료되면 배포를 통해 생성된 포드를 확인할 수 있습니다.

    kubectl get pods
    

서비스 배포

서비스는 포드 집합에 대한 단일 액세스 포인트를 제공합니다. 단일 포드에 액세스할 수 있지만 포드 수명이 짧으므로 서비스 주소를 사용해야 포드에 안정적으로 액세스할 수 있습니다. Hello World 앱에서 'hello' 서비스는 단일 IP 주소에서 hello-app 포드에 액세스하도록 부하 분산기를 정의합니다. 이 서비스는 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

    포드는 포드를 사용하는 서비스와 별도로 정의됩니다. Kubernetes는 라벨을 사용하여 서비스에서 주소를 지정하는 포드를 선택합니다. 라벨을 사용하면 하나의 서비스에서 다양한 복제본 세트의 포드에 주소를 지정할 수 있고 여러 서비스에서 개별 포드를 가리킬 수도 있습니다.

  2. Hello World 서비스를 만듭니다.

    kubectl apply -f service.yaml
    
  3. 서비스의 외부 IP 주소를 가져옵니다.

    kubectl get services
    

    IP 주소를 할당하는 데 최대 60초가 걸릴 수 있습니다. 외부 IP 주소는 hello 서비스의 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

또는 서비스의 외부 IP 주소에 curl을 호출할 수 있습니다.

curl EXTERNAL_IP

출력이 다음과 같이 표시됩니다.

Hello World!

삭제

이 페이지에서 사용한 리소스 비용이 Google Cloud 계정에 청구되지 않도록 하려면 다음 단계를 수행합니다.

Artifact Registry의 컨테이너 이미지뿐만 아니라 클러스터에서 실행 중인 Compute Engine 인스턴스에 대한 비용이 청구됩니다.

프로젝트 삭제

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에서 애플리케이션을 만들고 개발하고 실행하는 방법에 대한 자세한 내용은 다음을 참조하세요.