빠른 시작: 언어별 앱 배포

이 페이지에서는 다음을 수행하는 방법을 보여줍니다.

  1. Hello World 앱을 만듭니다.
  2. Cloud Build를 사용하여 앱을 컨테이너 이미지로 패키징합니다.
  3. Google Kubernetes Engine(GKE)에서 클러스터를 만듭니다.
  4. 클러스터에 컨테이너 이미지를 배포합니다.

샘플은 여러 언어로 표시되지만 표시된 언어 외에도 다른 언어를 사용할 수 있습니다.

시작하기 전에

  1. Google 계정에 로그인합니다.

    아직 계정이 없으면 새 계정을 등록하세요.

  2. Google Cloud Platform 프로젝트를 선택하거나 만듭니다.

    리소스 관리 페이지로 이동

  3. Google Cloud Platform 프로젝트에 결제가 사용 설정되어 있는지 확인하세요.

    결제 사용 설정 방법 알아보기

  4. Cloud Build and Google Kubernetes Engine APIs를 사용 설정합니다.

    APIs 사용 설정

  5. Cloud SDK 설치 및 초기화.
  6. 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 컨테이너에 패키징할 준비가 되고 Container 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 컨테이너에 패키징할 준비가 되고 Container 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 bootVersion=2.1.3.RELEASE \
        -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 컨테이너에 패키징할 준비가 되고 Container Registry에 업로드됩니다.

C#

  1. .NET Core SDK 2.2를 설치합니다. .NET Core SDK는 다음 단계에서 새로운 웹 프로젝트를 만들 때만 필요합니다. 나중에 설명하게 될 Dockerfile은 컨테이너에 모든 종속 항목을 로드합니다.

  2. 터미널에서 다음과 같이 새로운 빈 웹 프로젝트를 만듭니다.

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

  4. 제공 포트를 정의하기 위해 .UseUrls()의 포트 URL을 지정하여 Program.cs에서 CreateWebHostBuilder 정의를 업데이트합니다. 샘플에는 포트 8080이 표시되지만 다른 포트를 사용할 수 있습니다. 서버는 여기서 지정한 포트를 리슨해야 합니다.

    // Copyright (c) 2019 Google LLC.
    //
    // Licensed under the Apache License, Version 2.0 (the "License"); you may not
    // use this file except in compliance with the License. You may obtain a copy of
    // the License at
    //
    // http://www.apache.org/licenses/LICENSE-2.0
    //
    // Unless required by applicable law or agreed to in writing, software
    // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    // License for the specific language governing permissions and limitations under
    // the License.
    
    using System;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    
    namespace HelloGKE
    {
        public class Program
        {
            public static void Main(string[] args)
            {
                CreateWebHostBuilder(args).Build().Run();
            }
    
            public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
                WebHost.CreateDefaultBuilder(args)
                    .UseStartup<Startup>()
                    .UsePortEnvironmentVariable();
        }
    
        static class ProgramExtensions
        {
            // Google Cloud Run sets the PORT environment variable to tell this
            // process which port to listen to.
            public static IWebHostBuilder UsePortEnvironmentVariable(
                this IWebHostBuilder builder)
            {
                string port = Environment.GetEnvironmentVariable("PORT");
                if (!string.IsNullOrEmpty(port))
                {
                    builder.UseUrls($"http://0.0.0.0:{port}");
                }
                return builder;
            }
        }
    }
    

앱이 완료되어 Docker 컨테이너에 패키징할 준비가 되고 Container 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 컨테이너에 패키징할 준비가 되고 Container Registry에 업로드됩니다.

Ruby

  1. helloworld-gke라는 새 디렉터리를 만든 후 이 위치로 디렉터리를 변경합니다.

    mkdir helloworld-gke
    cd helloworld-gke
    
  2. app.rb라는 파일을 만든 후 이 파일에 다음 코드를 붙여넣습니다.

    require 'sinatra'
    
    set :bind, '0.0.0.0'
    set :port, ENV['PORT'] || '8080'
    
    get '/' do
      target = ENV['TARGET'] || 'World'
      "Hello #{target}!\n"
    end
    

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

  3. Gemfile이라는 파일을 만든 후 이 파일에 다음을 복사합니다.

    source 'https://rubygems.org'
    
    gem 'sinatra'
    gem 'rack', '>= 2.0.6'
    

Cloud Build로 앱 컨테이너화

  1. 샘플 앱을 컨테이너화하려면 Dockerfile이라는 새 파일을 소스 파일과 동일한 디렉터리에 만든 후 이 파일에 다음 콘텐츠를 복사합니다.

    Go

    # Use the offical Golang 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.12 as builder
    
    # Copy local code to the container image.
    WORKDIR /app
    COPY . .
    
    # Build the command inside the container.
    RUN CGO_ENABLED=0 GOOS=linux go build -v -o helloworld
    
    # 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 alpine
    RUN apk add --no-cache ca-certificates
    
    # Copy the binary to the production image from the builder stage.
    COPY --from=builder /app/helloworld /helloworld
    
    # Run the web service on container startup.
    CMD ["/helloworld"]
    

    Node.js

    # Use the official lightweight Node.js 10 image.
    # https://hub.docker.com/_/node
    FROM node:10-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 --only=production
    
    # 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__
    

    자바

    # 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.
    # https://hub.docker.com/r/microsoft/dotnet
    
    FROM mcr.microsoft.com/dotnet/core/sdk:2.2-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 . ./
    
    # 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/core/aspnet:2.2-alpine
    WORKDIR /app
    
    COPY --from=build /app/out ./
    CMD ["dotnet", "HelloGKE.dll"]
    

    로컬 파일이 컨테이너 빌드 프로세스에 영향을 미치지 않도록 .dockerignore 파일을 추가합니다.

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

    PHP

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

    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 ./
    RUN bundle install
    
    # Copy local code to the container image.
    COPY . ./
    
    # Run the web service on container startup.
    CMD ["ruby", "./app.rb"]
    

  2. GCP 프로젝트 ID를 가져옵니다.

    gcloud config get-value project
    

    Cloud Build를 사용하여 컨테이너 이미지를 빌드합니다. 이는 Google Cloud Platform(GCP)에서 수행된다는 점을 제외하고는 docker builddocker push를 실행하는 것과 유사합니다. PROJECT_ID를 GCP ID로 바꿉니다.

    gcloud builds submit --tag gcr.io/PROJECT_ID/helloworld-gke .
    

    이미지는 Container Registry에 저장됩니다.

Kubernetes Engine 클러스터 만들기

GKE 클러스터는 단일 GKE 클러스터처럼 작동하는 관리형 Compute Engine 가상 머신 모음입니다. 이 가이드에서는 단일 노드를 사용합니다.

  1. 클러스터를 만듭니다. YOUR_GCP_ZONE을 클러스터를 호스팅할 GCP 영역으로 바꿉니다. 전체 목록은 위치 및 리전을 참조하세요.

    gcloud container clusters create helloworld-gke \
       --num-nodes 1 \
       --enable-basic-auth \
       --issue-client-certificate \
       --zone YOUR_GCP_ZONE
    
  2. 클러스터에 액세스할 수 있는지 확인합니다. 다음 명령어는 준비 및 실행 중이며 액세스 권한이 있음을 나타내는 컨테이너 클러스터의 노드를 나열합니다.

    kubectl get nodes
    

    오류가 발생하면 Kubernetes 문제해결 가이드를 참조하세요.

GKE에 배포

만든 GKE 클러스터에 앱을 배포하려면 두 개의 Kubernetes 객체가 필요합니다.

  1. 앱을 정의하는 배포
  2. 앱에 액세스하는 방법을 정의하는 서비스

앱 배포

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

  1. 다른 파일과 동일한 디렉터리에 deployment.yaml 파일을 만들고 $GCLOUD_PROJECT를 GCP 프로젝트 ID로 바꿔 다음 콘텐츠를 복사합니다.

    # This file configures the hello-world app which serves public web traffic.
    apiVersion: extensions/v1beta1
    kind: Deployment
    metadata:
      name: helloworld-gke
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: hello
      template:
        metadata:
          labels:
            app: hello
        spec:
          containers:
          - name: hello-app
            # Replace $GCLOUD_PROJECT with your project ID
            image: gcr.io/$GCLOUD_PROJECT/helloworld-gke:latest
            # This app listens on port 8080 for web traffic by default.
            ports:
            - containerPort: 8080
            env:
              - name: PORT
                value: "8080"
    
  2. 클러스터에 리소스를 배포합니다.

    kubectl apply -f deployment.yaml
    
  3. 배포 상태를 추적합니다.

    kubectl get deployments
    

    배포에서 DESIRED pod와 동일한 수의 AVAILABLE pod를 사용할 수 있게 되면 배포는 완료됩니다.

    NAME               DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
    hello-deployment   1         1         1            1           20s
    

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

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

    kubectl get pods
    

서비스 배포

서비스는 pod 모음에 대한 단일 액세스 포인트를 제공합니다. 단일 pod에 액세스할 수는 있지만 pod는 수명이 짧기 때문에 서비스 주소를 사용해서만 안정적으로 액세스할 수 있습니다. Hello World 앱에서 'hello' 서비스는 단일 IP 주소에서 hello-app pod에 액세스하도록 부하 분산기를 정의합니다. 이 서비스는 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를 사용하는 서비스와 별도로 정의됩니다. Kubernetes는 라벨을 사용하여 서비스에서 주소를 지정하는 pod를 선택합니다. 라벨을 사용하면 하나의 서비스에서 다양한 복제본 세트의 pod에 주소를 지정할 수 있고 여러 서비스에서 개별 pod를 가리킬 수도 있습니다.

  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 주소를 사용하여 웹브라우저에 앱을 로드하고 실행 중인 앱을 확인합니다.

# Example cURL call to your running appication on GKE
$ kubectl get service hello \
    -o=custom-columns=NAME:.status.loadBalancer.ingress[*].ip --no-headers
35.111.111.11
$ curl 35.111.111.11
Hello World!

삭제

이 빠른 시작에서 사용한 리소스 비용이 GCP 계정에 청구되지 않도록 다음을 수행합니다.

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

프로젝트 삭제

GCP 프로젝트를 삭제하면 해당 프로젝트에서 사용되는 모든 리소스에 대한 청구가 중단됩니다.

  1. GCP Console에서 프로젝트 페이지로 이동합니다.

    프로젝트 페이지로 이동

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

클러스터 및 컨테이너 삭제

프로젝트를 보존하면서 이 가이드에서 사용된 리소스만 삭제하려면 클러스터와 이미지를 삭제합니다.

gcloud 명령줄 도구를 사용하여 클러스터를 삭제하려면 다음 명령어를 실행합니다.

gcloud container clusters delete helloworld-gke

Container Registry 저장소 중 하나에서 이미지를 삭제하려면 다음 명령어를 실행합니다.

gcloud container images delete gcr.io/[PROJECT-ID]/helloworld-gke

다음 단계

Kubernetes에 대한 자세한 내용은 다음을 참조하세요.

GKE에 배포하는 것에 대한 자세한 내용은 다음을 참조하세요.

Cloud Code를 사용하여 IDE에서 직접 GKE에 배포하는 방법에 대한 자세한 내용은 다음을 참조하세요.

이 페이지가 도움이 되었나요? 평가를 부탁드립니다.

다음에 대한 의견 보내기...

Kubernetes Engine 문서