Jib로 컨테이너화된 Micronaut 애플리케이션을 Google Kubernetes Engine에 배포

1. 개요

Micronaut 정보

Micronaut는 모듈식으로 쉽게 테스트할 수 있는 마이크로서비스 및 서버리스 애플리케이션을 빌드하기 위한 최신 JVM 기반 전체 스택 프레임워크입니다. Micronaut는 최소한의 메모리 사용량으로 빠른 처리량과 우수한 시작 시간을 제공하는 것을 목표로 합니다. 개발자는 Java, Groovy 또는 Kotlin으로 Micronaut를 사용하여 개발할 수 있습니다.

Micronaut는 다음을 제공합니다.

  • 빠른 시작 시간 및 낮은 메모리 소비 - 리플렉션 기반 IoC 프레임워크는 코드의 모든 필드, 메서드, 생성자에 대한 리플렉션 데이터를 로드하고 캐시하는 반면 Micronaut를 사용하면 애플리케이션 시작 시간과 메모리 소비가 코드베이스의 크기에 제한되지 않습니다.
  • 선언적, 반응형, 컴파일 시간 HTTP 클라이언트: 컴파일 시간에 구현되는 반응형 HTTP 클라이언트를 선언적으로 빌드하여 메모리 소비를 줄입니다.
  • Netty 기반의 비차단 HTTP 서버: Micronaut의 HTTP 서버는 학습 곡선이 완만하여 HTTP 클라이언트가 사용할 수 있는 API를 최대한 쉽게 노출할 수 있습니다.
  • 빠르고 쉬운 테스트: 단위 테스트에서 서버와 클라이언트를 쉽게 가동하고 즉시 실행할 수 있습니다.
  • 효율적인 컴파일 시간 종속 항목 삽입 및 AOP: Micronaut는 리플렉션을 사용하지 않는 간단한 컴파일 시간 측면 지향 프로그래밍 API를 제공합니다.
  • 완전한 반응형 및 비차단 앱 빌드 - Micronaut는 RxJava, Reactor를 비롯한 Reactive Streams를 구현하는 모든 프레임워크를 지원합니다.

자세한 내용은 Micronaut 웹사이트를 참고하세요.

Kubernetes 정보

Kubernetes는 노트북에서 고가용성 다중 노드 클러스터, 퍼블릭 클라우드에서 온프레미스 배포, 가상 머신에서 베어 메탈에 이르기까지 다양한 환경에서 실행할 수 있는 오픈소스 프로젝트입니다.

이 실습에서는 간단한 Groovy 기반 Micronaut 마이크로서비스를 Kubernetes Engine에서 실행되는 Kubernetes에 배포합니다.

이 Codelab의 목표는 Kubernetes에서 실행되는 복제 서비스로 마이크로서비스를 실행하는 것입니다. 머신에서 개발한 코드를 가져와 Docker 컨테이너 이미지로 변환한 다음 Kubernetes Engine에서 해당 이미지를 실행합니다.

다음은 Codelab의 다양한 부분이 어떻게 연동되는지 이해하기 위한 그림입니다. Codelab을 진행하면서 그림을 참조 자료로 사용하세요. Codelab을 완료하면 이해할 수 있게 되니 지금은 무시하고 넘어가도 좋습니다.

Kubernetes Codelab Diagram 1 (2).png

이 Codelab에서는 Kubernetes Engine (Compute Engine에서 실행되는 Google 호스팅 버전의 Kubernetes)과 같은 관리형 환경을 사용하여 기본 인프라를 설정하는 대신 Kubernetes를 경험하는 데 집중합니다.

개발 노트북과 같은 로컬 머신에서 Kubernetes를 실행하는 데 관심이 있다면 Minikube를 살펴보는 것이 좋습니다. 이를 통해 개발 및 테스트 목적으로 단일 노드 Kubernetes 클러스터를 간단하게 설정할 수 있습니다. 원하는 경우 Minikube를 사용하여 이 Codelab을 진행할 수 있습니다.

Jib 정보

Jib는 Java 애플리케이션용 Docker 및 OCI 이미지를 빌드할 수 있는 오픈소스 도구입니다. Maven 및 Gradle용 플러그인과 Java 라이브러리로 제공됩니다.

Jib의 목표는 다음과 같습니다.

  • 빠름: 변경사항을 빠르게 배포합니다. Jib은 애플리케이션을 여러 레이어로 분리하여 클래스에서 종속 항목을 분리합니다. 이제 Docker가 전체 Java 애플리케이션을 다시 빌드할 때까지 기다릴 필요 없이 변경된 레이어만 배포하면 됩니다.
  • 재현 가능 - 동일한 콘텐츠로 컨테이너 이미지를 다시 빌드하면 항상 동일한 이미지가 생성됩니다. 불필요한 업데이트를 다시 트리거하지 마세요.
  • 데몬 없음 - CLI 종속 항목을 줄입니다. Maven 또는 Gradle 내에서 Docker 이미지를 빌드하고 원하는 레지스트리로 푸시합니다. Dockerfile을 작성하고 docker build/push를 호출할 필요가 없습니다.

Jib에 관한 자세한 내용은 GitHub 프로젝트 페이지를 참고하세요.

이 튜토리얼 정보

이 튜토리얼에서는 Jib 도구의 샘플 코드를 사용하여 Java 애플리케이션용 컨테이너를 빌드합니다.

샘플은 Micronaut 프레임워크와 Apache Groovy 프로그래밍 언어를 사용하는 간단한 hello world 서비스입니다.

학습할 내용

  • Jib을 사용하여 간단한 Java 애플리케이션을 Docker 컨테이너로 패키징하는 방법
  • Kubernetes Engine에서 Kubernetes 클러스터를 만드는 방법
  • Kubernetes Engine의 Kubernetes에 Micronaut 서비스를 배포하는 방법
  • 서비스를 확장하고 업그레이드를 출시하는 방법
  • Kubernetes 그래픽 대시보드에 액세스하는 방법

필요한 항목

  • Google Cloud Platform 프로젝트
  • 브라우저(Chrome, Firefox 등)
  • Vim, EMACs, Nano 등의 표준 Linux 텍스트 편집기에 관한 기본 지식

본 가이드를 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

HTML/CSS 웹 앱 빌드 경험을 평가해 주세요.

초급 중급 고급

귀하의 Google Cloud Platform 서비스 사용 경험을 평가해 주세요.

초급 중급 고급

2. 설정 및 요구사항

자습형 환경 설정

  1. Cloud Console에 로그인하고 새 프로젝트를 만들거나 기존 프로젝트를 다시 사용합니다. (Gmail 또는 G Suite 계정이 없으면 만들어야 합니다.)

dMbN6g9RawQj_VXCSYpdYncY-DbaRzr2GbnwoV7jFf1u3avxJtmGPmKpMYgiaMH-qu80a_NJ9p2IIXFppYk8x3wyymZXavjglNLJJhuXieCem56H30hwXtd8PvXGpXJO9gEUDu3cZw

ci9Oe6PgnbNuSYlMyvbXF1JdQyiHoEgnhl4PlV_MFagm2ppzhueRkqX4eLjJllZco_2zCp0V0bpTupUSKji9KkQyWqj11pqit1K1faS1V6aFxLGQdkuzGp4rsQTan7F01iePL5DtqQ

8-tA_Lheyo8SscAVKrGii2coplQp2_D1Iosb2ViABY0UUO1A8cimXUu6Wf1R9zJIRExL5OB2j946aIiFtyKTzxDcNnuznmR45vZ2HMoK3o67jxuoUJCAnqvEX6NgPGFjCVNgASc-lg

모든 Google Cloud 프로젝트에서 고유한 이름인 프로젝트 ID를 기억하세요(위의 이름은 이미 사용되었으므로 사용할 수 없습니다). 이 ID는 나중에 이 Codelab에서 PROJECT_ID라고 부릅니다.

  1. 그런 후 Google Cloud 리소스를 사용할 수 있도록 Cloud Console에서 결제를 사용 설정해야 합니다.

이 Codelab 실행에는 많은 비용이 들지 않습니다. 이 가이드를 마친 후 비용이 결제되지 않도록 리소스 종료 방법을 알려주는 '삭제' 섹션의 안내를 따르세요. Google Cloud 새 사용자에게는 미화 $300 상당의 무료 체험판 프로그램에 참여할 수 있는 자격이 부여됩니다.

3. Micronaut 샘플 소스 코드 가져오기

Cloud Shell이 실행되면 명령줄을 사용하여 홈 디렉터리에 예시 소스 코드를 클론하고 샘플 서비스가 포함된 디렉터리로 cd할 수 있습니다.

$ git clone https://github.com/GoogleContainerTools/jib.git
$ cd jib/examples/micronaut/

4. 코드 간단히 살펴보기

Micronaut 간단한 서비스는 악명 높은 Hello World 메시지를 출력하는 컨트롤러로 구성됩니다.

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello World"
    }
}

HelloController 컨트롤러는 /hello 경로의 요청에 응답하고 index() 메서드는 HTTP GET 요청을 수락합니다.

Spock 테스트 클래스를 사용하여 출력에 올바른 메시지가 제공되는지 확인할 수도 있습니다.

class HelloControllerSpec extends Specification {
    @Shared
    @AutoCleanup
    EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer)

    @Shared
    @AutoCleanup
    RxHttpClient client = embeddedServer.applicationContext.createBean(RxHttpClient, embeddedServer.getURL()) 

    void "test hello world response"() {
        when:
        HttpRequest request = HttpRequest.GET('/hello')
        String rsp  = client.toBlocking().retrieve(request)

        then:
        rsp == "Hello World"
    }
}

단순한 단위 테스트 이상으로 이 테스트는 프로덕션에서 실행되는 것과 동일한 Micronaut 서버 스택 (Netty 프레임워크 기반)을 실제로 실행합니다. 따라서 제품에서 코드의 동작은 테스트에서와 동일합니다.

테스트를 실행하려면 다음 명령어를 실행하여 모든 것이 정상인지 확인할 수 있습니다.

./gradlew test

5. 애플리케이션을 로컬로 실행

다음 Gradle 명령어를 사용하여 Micronaut 서비스를 정상적으로 시작할 수 있습니다.

$ ./gradlew run

애플리케이션이 시작되면 작은 + 아이콘을 사용하여 추가 Cloud Shell 인스턴스를 열고 curl을 사용하여 예상 출력이 표시되는지 확인할 수 있습니다.

$ curl localhost:8080/hello

간단한 'Hello World' 메시지가 표시됩니다.

6. Jib을 사용하여 애플리케이션을 Docker 컨테이너로 패키징

그런 다음 Kubernetes에서 실행되도록 앱을 준비합니다. 이를 위해 Dockerfile를 직접 건드릴 필요가 없으므로 Jib을 활용하여 어려운 작업을 대신 처리합니다.

명령어를 실행하여 컨테이너를 빌드해 보겠습니다.

$ ./gradlew jibDockerBuild

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

Tagging image with generated image reference micronaut-jib:0.1. If you'd like to specify a different tag, you can set the jib.to.image parameter in your build.gradle, or use the --im
age=<MY IMAGE> commandline flag.

Containerizing application to Docker daemon as micronaut-jib:0.1...
warning: Base image 'gcr.io/distroless/java' does not use a specific image digest - build may not be reproducible
Getting base image gcr.io/distroless/java...
Building dependencies layer...
Building resources layer...
Building classes layer...
Finalizing...

Container entrypoint set to [java, -cp, /app/resources:/app/classes:/app/libs/*, example.micronaut.Application]
Loading to Docker daemon...

Built image to Docker daemon as micronaut-jib:0.1

이제 이미지가 빌드되었으므로 Cloud Shell의 첫 번째 탭에서 Docker 이미지를 실행하여 친근한 인사말이 표시되는지 확인해 보겠습니다.

$ docker run -it -p 8080:8080 micronaut-jib:0.1
16:57:20.255 [main] INFO  i.m.context.env.DefaultEnvironment - Established active environments: [cloud, gcp]
16:57:23.203 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 2926ms. Server Running: http://97b7d76ccf3f:8080

서비스가 실행 중이므로 이제 두 번째 Cloud Shell 탭에서 curl 명령어를 실행하여 예상대로 작동하는지 확인할 수 있습니다.

$ curl localhost:8080/hello
Hello World

Cloud Shell에서 Ctrl+C를 눌러 컨테이너를 중지할 수 있습니다.

7. 컨테이너화된 서비스를 레지스트리에 푸시

이제 이미지가 의도한 대로 작동하므로 모든 Google Cloud 프로젝트는 물론 Google Cloud Platform 외부에서도 액세스할 수 있는 Docker 이미지용 비공개 저장소인 Google Container Registry로 이미지를 푸시합니다.

레지스트리로 푸시하기 전에 도구 > Container Registry로 이동하여 Container Registry가 프로젝트에 사용 설정되어 있는지 확인합니다. 사용 설정되지 않은 경우 다음 대화상자가 표시됩니다. 'Container Registry API 사용 설정'을 클릭하여 사용 설정하세요.

ac812e6260ac7dfb.png

레지스트리가 준비되면 이미지를 레지스트리에 푸시하기 위해 다음 명령어를 실행합니다.

$ gcloud auth configure-docker
$ docker tag micronaut-jib:0.1 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

위 명령어를 사용하면 gcloud SDK가 Container Registry 인스턴스로 이미지를 푸시하도록 Docker를 구성하고 승인하여 레지스트리의 위치를 가리키도록 이미지에 태그를 지정한 후 레지스트리로 푸시할 수 있습니다.

모든 것이 정상적으로 진행되면 잠시 후 콘솔(도구 > Container Registry)에 컨테이너 이미지가 표시됩니다. 이제 Kubernetes가 액세스하고 조정할 수 있는 프로젝트 전체 Docker 이미지가 생겼습니다.

12224c4e42183b4e.png

8. 클러스터 만들기

이제 Kubernetes Engine 클러스터를 만들 준비가 되었습니다. 하지만 그 전에 웹 콘솔의 Google Kubernetes Engine 섹션으로 이동하여 시스템이 초기화될 때까지 기다립니다 (몇 초밖에 걸리지 않음).

20c0587c0108b8ba.png

클러스터는 Google에서 관리하는 Kubernetes 마스터 API 서버와 워커 노드 집합으로 구성됩니다. 워커 노드는 Compute Engine 가상 머신입니다. CloudShell 세션에서 gcloud CLI를 사용하여 n1-standard-1 노드 2개로 클러스터를 만듭니다. 이 작업은 완료하는 데 몇 분 정도 걸릴 수 있습니다.

$ gcloud container clusters create hello-cluster \
  --num-nodes 2 \
  --machine-type n1-standard-1 \
  --zone us-central1-c

결국 생성된 클러스터가 표시됩니다.

Creating cluster hello-cluster in us-central1-c...done.
Created [https://container.googleapis.com/v1/projects/mn-gke-test/zones/us-central1-c/clusters/hello-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-c/hello-cluster?project=mn-gke-test
kubeconfig entry generated for hello-cluster.
NAME           LOCATION       MASTER_VERSION  MASTER_IP       MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
hello-cluster  us-central1-c  1.9.7-gke.7     35.239.224.115  n1-standard-1  1.9.7-gke.7   2          RUNNING

이제 Google Kubernetes Engine에서 제공하는 Kubernetes 클러스터가 완전히 작동합니다.

d9e1e314769753e7.png

이제 컨테이너화된 애플리케이션을 Kubernetes 클러스터에 배포할 차례입니다. 이제부터는 Cloud Shell 환경에 이미 설정된 kubectl 명령줄을 사용합니다. 이 Codelab의 나머지 부분에서는 Kubernetes 클라이언트와 서버 버전이 모두 1.2 이상이어야 합니다. kubectl version를 사용하면 명령어의 현재 버전이 표시됩니다.

9. Kubernetes에 애플리케이션 배포

Kubernetes 배포는 방금 만든 컨테이너 이미지를 사용하여 애플리케이션의 여러 인스턴스를 만들고, 관리하고, 확장할 수 있습니다. kubectl create deployment 명령어를 사용하여 Kubernetes에 애플리케이션을 배포해 보겠습니다.

$ kubectl create deployment hello-micronaut \
  --image=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.1

방금 만든 배포를 보려면 다음을 실행하면 됩니다.

$ kubectl get deployments
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   1         1         1            1           5m

배포에 의해 생성된 애플리케이션 인스턴스를 보려면 다음 명령어를 실행합니다.

$ kubectl get pods
NAME                               READY     STATUS    RESTARTS   AGE
hello-micronaut-5647fb98c5-lh5h7   1/1       Running   0          5m

이제 Kubernetes의 제어 하에 컨테이너가 실행되고 있지만 외부에서 액세스할 수 있도록 해야 합니다.

10. 외부 트래픽 허용하기

기본적으로 포드는 클러스터 내에서 내부 IP로만 액세스할 수 있습니다. hello-micronaut 컨테이너에 Kubernetes 가상 네트워크 외부에서 액세스할 수 있게 하려면 포드를 Kubernetes 서비스로 노출해야 합니다.

Cloud Shell에서 kubectl expose 명령어와 --type=LoadBalancer 플래그를 합쳐서 포드를 공용 인터넷에 노출할 수 있습니다. 이 플래그는 외부에서 액세스 가능한 IP를 만드는 데 필요합니다.

$ kubectl expose deployment hello-micronaut --type=LoadBalancer --port=8080

이 명령어에서 사용된 플래그는 기본 인프라가 제공하는 부하 분산기 (여기에서는 Compute Engine 부하 분산기)를 사용하도록 명시합니다. 포드가 아니라 배포를 직접 노출한다는 점을 참고하세요. 이로 인해 배포가 관리하는 모든 포드에 걸쳐 서비스가 트래픽을 부하 분산하게 됩니다. 이번 예시에서는 포드가 1개이지만 나중에 복제본을 더 추가하게 됩니다.

Kubernetes 마스터에서는 Google Cloud Platform 외부에서 서비스에 완전하게 액세스할 수 있도록 부하 분산기, 관련된 Compute Engine 전달 규칙, 타겟 풀 및 방화벽 규칙을 만듭니다.

공개적으로 액세스할 수 있는 서비스의 IP 주소를 찾으려면 kubectl에 모든 클러스터 서비스를 나열하도록 요청하면 됩니다.

$ kubectl get services
NAME              TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE
hello-micronaut   LoadBalancer   10.39.243.251   aaa.bbb.ccc.ddd 8080:30354/TCP   1m
kubernetes        ClusterIP      10.39.240.1     <none>          443/TCP          31m

서비스에 IP 주소가 2개 있으며 2개 모두 포트 8080을 제공합니다. 하나는 클라우드 가상 네트워크에서만 볼 수 있는 내부 IP이고 다른 하나는 부하 분산된 외부 IP입니다. 이 예시에서 외부 IP 주소는 aaa.bbb.ccc.ddd입니다.

이제 브라우저에서 http://<EXTERNAL_IP>:8080/hello 주소로 접속하면 서비스를 이용할 수 있습니다.

11. 서비스 확장하기

Kubernetes의 강력한 특징 중 하나는 애플리케이션을 손쉽게 확장할 수 있다는 점입니다. 애플리케이션에 용량이 더 필요하다고 가정해 보겠습니다. 복제본 컨트롤러에 애플리케이션 인스턴스의 새로운 복제본 수를 관리하도록 지정하면 됩니다.

$ kubectl scale deployment hello-micronaut --replicas=3
deployment.extensions "hello-micronaut" scaled

$ kubectl get deployment
NAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
hello-micronaut   3         3         3            3           16m

여기서 선언적 접근 방식에 주목하세요. 새로운 인스턴스를 시작하고 중지하는 대신 항상 실행 중이어야 하는 인스턴스의 개수를 선언합니다. Kubernetes 조정 루프는 현재 상황이 요청과 일치하는지 확인하며 필요시 조치를 취합니다.

12. 서비스 업그레이드하기

프로덕션에 배포한 애플리케이션에 버그 수정이나 추가 기능이 필요한 시점이 있습니다. Kubernetes를 활용하여 사용자에게 영향을 주지 않고 새로운 버전을 프로덕션에 배포할 수 있습니다.

먼저 애플리케이션을 수정해 보겠습니다. Cloud Shell에서 코드 편집기를 엽니다.

5aee8f3d1e003571.png

/jib/examples/micronaut/src/main/groovy/example/micronaut/HelloController.groovy로 이동하여 응답 값을 업데이트합니다.

@Controller("/hello")
class HelloController {
    @Get("/")
    String index() {
        "Hello Kubernetes World"
    }
}

/jib/examples/micronaut/build.gradle에서는 이 줄을 업데이트하여 이미지 버전을 0.1에서 0.2로 업그레이드합니다.

version '0.2'

그런 다음 최신 변경사항을 사용하여 애플리케이션을 다시 빌드하고 패키징합니다.

$ ./gradlew jibDockerBuild

이미지에 태그를 지정하고 컨테이너 이미지 레지스트리로 푸시합니다.

$ docker tag micronaut-jib:0.2 \
         gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2
$ docker push gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

이제 Kubernetes에서 복제본 컨트롤러를 새 버전의 애플리케이션으로 원활하게 업데이트할 준비가 되었습니다. 실행 중인 컨테이너의 이미지 라벨을 변경하려면 기존 hello-micronaut deployment를 수정하고 이미지를 gcr.io/PROJECT_ID/micronaut-jib:0.1에서 gcr.io/PROJECT_ID/micronaut-jib:0.2로 변경해야 합니다.

kubectl set image 명령어를 사용하여 Kubernetes에 전체 클러스터에 새 버전의 애플리케이션을 한 번에 하나의 인스턴스로 순차 업데이트하도록 요청할 수 있습니다.

$ kubectl set image deployment/hello-micronaut \
          micronaut-jib=gcr.io/$GOOGLE_CLOUD_PROJECT/micronaut-jib:0.2

deployment.apps "hello-micronaut" image updated

http://EXTERNAL_IP:8080을 다시 확인하여 새 응답이 반환되는지 확인합니다.

13. 롤백

죄송합니다. 새 버전의 애플리케이션에 문제가 있나요? 새 버전에 오류가 포함되어 있어 빠르게 롤백해야 할 수도 있습니다. Kubernetes를 사용하면 이전 상태로 쉽게 롤백할 수 있습니다. 다음을 실행하여 애플리케이션을 롤백합니다.

$ kubectl rollout undo deployment/hello-micronaut

서비스의 출력을 살펴보면 초기 'Hello World' 메시지로 돌아갑니다.

14. 요약

이 단계에서는 간단한 Apache Groovy 기반 Micronaut hello world 서비스를 설정하고 Cloud Shell 내에서 직접 실행하고, Jib으로 컨테이너로 패키징하고, Google Kubernetes Engine에 배포했습니다.

15. 축하합니다.

Google Kubernetes Engine의 Kubernetes에 새로운 Apache Groovy / Micronaut 웹 기반 마이크로서비스를 빌드하고 배포하는 방법을 알아봤습니다.

자세히 알아보기

라이선스

이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.