2022년 2월 7일 월요일

Apache Camel 애플리케이션 만들기

Apache Camel 애플리케이션 만들기

들어가며

일반적으로 애플리케이션에서 프레임워크를 이용하려면, 개발 소스 프로젝트에 프레임워크가 요구하는 의존, 설정, 코드 등을 입력해야 합니다. 그러므로 새로운 프레임워크를 이용하려는 개발자는 반드시, 이 내용을 프레임워크 관련 자료나 책 등을 학습해 애플리케이션 프로젝트에 적용해야 합니다. Java 애플리케이션 프레임워크로 가장 장 알려진 Spring 프레임워크나 Spring Boot 프레임워크도 오류 없이 기대하는 동작을 실행시키려면, Java 메인 클래스, 설정, 클래스, 메서드, 애노테이션, application.properties 나 application.yaml 등 애플리케이션 개발에 필요한 여러 가지 “관례(convention)” 와 “설정(configuration)”을 이해하고 올바르게 입력해야 합니다.

이런 과정은 Apache Camel 프레임워크를 사용할 때도 동일하게 적용됩니다. 그런데 Apache Camel 프레임워크를 처음 접한 개발자들이 올바른 Apache Camel 애플리케이션을 만들기는 그렇게 간단하지 않습니다. 그 이유는Apache Camel 프레임워크도 “관례(convention)”와 “설정(configuration)”을 정확하게 사용해야 하는데, 이런 “관례(convention)”와 “설정(configuration)” 은 해당 기술을 깊은 이해해야 체득되는 것이기 때문입니다.

Apache Camel 프레임워크를 접한 개발자들이 “관례(convention)”와 “설정(configuration)” 이 잘 구성된 애플리케이션 프로젝트부터 개발을 시작하는 것은 굉장히 중요한 출발점입니다. 그럼에도 책이나 문서들은 이 출발점에 대해 다양하게 설명하지만 선택할 수 없는 차림표처럼 제시할 뿐, 어떤 것이 최선의 “관례(convention)”와 “설정(configuration)” 인지는 잘 설명하고 있지 않습니다.

이런 점에서 이 글 Apache Camel 프레임워크 개발자들에게 유용한 Apache Camel 애플리케이션 프로젝트의 출발점을 제공합니다. 이를 위해 이 글은 Apache Camel 애플리케이션 개발을 시작하는 다양한 방법 중 주요 방법을 설명하고, 그 중 일관된 “관례(convention)”와 “설정(configuration)” 를 포함한 Apache Camel 애플리케이션 프로젝트를 생성하게 해주는 필자의 Spring Boot Apache Camel XML Archetype 을 설명합니다.

Apache Camel 이란?

  Apache Camel 은 잘 정립된 기업 통합 패턴(Enterprise Integraion Patterns) 개념을 이용해, 쉽고 표준적인 방법으로 애플리케이션을 통합할 수 있게 하는 Java로 작성된 프레임워크입니다. Apache Camel 의 컴포넌트는 데이터베이스, 메시지 브로커, HTTP 애플리케이션, 파일 시스템 등 다양한 기술의 엔드포인트를 통합(접속)시킬 수 있습니다. 이미 이런 컴포넌트가 300 개 이상 구현되어 있습니다. 예를 들어, Twitter, AWS, Azure, Kafka, Google, File, FTP, SOAP, Rest API, DBMS, No Sql 등등 대부분의 주요 플랫폼과 기술들의 컴포넌트를 포함합니다. 이런 점에서 Apache Camel 은 기업 통합(Enterprise Integration)을 위한 완벽한 스위스 칼(Swiss knife)입니다.

Apache Camel 프로젝트 만들기

Apache Camel 은 독립적으로 실행될 수도 있으나, 다양한 애플리케이션 프레임워크와 결합해 실행하는 내장형 통합 프레임워크 입니다. 그 중 가장 현대화된 방식으로 Spring Boot 나 Quarkus 애플리케이션에 Apache Camel 을 내장해 애플리케이션을 실행하는 것입니다. 그 외 더 다양한 방식으로 실행할 수 있으나, 실무적으로 이 두 방식이 가장 유용성이 높을 것으로 보입니다. 이 글은 Spring Boot 나 Quarkus 와 결합한 Apache Camel 애플리케이션 프로젝트를 만드는 법을 소개합니다. Apache Camel 애플리케이션 소스 생성하는 방법으로 웹 기반 생성과 Maven Archetype 을 이용한 방법을 소개합니다. 다른 방법에 관심있는 독자는 독자 자신의 탐구 과제로 남기겠습니다.

실행 환경

Apache Camel 애플리케이션 생성 환경은 다음과 같습니다. 사전에 설치돼 있어야 합니다.

  • Linux 또는 macOS 권장
  • JDK 8+
  • Maven 3.5+

Spring initaializr 를 이용한 프로젝트 만들기

Apache Camel 애플리케이션 프로젝트는 Spring initaializr(https://start.spring.io/) 사이트를 이용해 만들 수 있습니다. 이 사이트를 이용하면, 생성한 애플리케이션 프로젝트에서 아마치 카멜과 Spring Boot 프레임워크를 함께 이용할 수 있습다. Apache Camel 은 Spring 프레임워크와 잘 결합되고, Spring 프레임워크에서도 Apache Camel 을 이니셜라이져 사이트에서 내장할 수 있게 지원합니다. Spring 이니셜라이저 사이트에서 다음 정보를 입력해 Apache Camel 의존을 포함하면 Apache Camel 애플리케이션을 만들 수 있습니다.

이 글에서는 Spring Initializr 에서 생성하는 프로젝트 형식은 Java Maven 프로젝트로 지정했습니다. Gradle 프로젝트로도 생성할 수 있으나, Apache Camel 프로젝트는 Maven 프로젝트를 선호하기 때문입니다. 그 이유는 “Simple is Better” 원칙을 따르기 때문일 것입니다.

  • Project : Maven Project

  • Spring Boot: 2.6.3

  • Project Metadata

    • Group: org.example
    • Artifact: camel-sprig-hello
    • Name: camel-spring-hello
    • Description: Demo project for Spring Boot
    • Package name: org.example.camel-spring-hello
    • Packaging: Jar
    • Java: 11
  • Dependencies: Spring Web, Apache Camel

    camel-spring-hello

위 값들을 입력 후, “GENERATE” 버튼을 클릭하면 camel-hello.zip 파일로 Apache Camel 애플리케이션 프로젝트 소스를 다운로드 합니다.

생성된 camel-spring-hello 프로젝트 구조는 다음과 같습니다.

camel-spring-hello  
├── HELP.md  
├── mvnw  
├── mvnw.cmd  
├── pom.xml  
└── src  
 ├── main 
 │   ├── java 
 │   │   └── org 
 │   │       └── example 
 │   │           └── camelspringhello 
 │   │               └── CamelSpringHelloApplication.java 
 │   └── resources 
 │       └── application.properties 
 └── test 
     └── java 
         └── org 
             └── example 
                 └── camelspringhello 
                     └── CamelSpringHelloApplicationTests.java

Spring Initializr 가 생성한 Apache Camel 프로젝트의 Apache Camel 프레임워크 버전은 Spring Boot 버전에 따라 결정됩니다. 그러므로 원하는 Apache Camel 프레임워크 버전을 맞추려면 Spring Boot 버전을 조정할 필요가 있습니다.

생성된 Apache Camel 프로젝트는 Spring Boot Maven 프로젝트로 생성됐으므로, 다음 명령으로 실행할 수 있습니다.

mvn spring-boot:run

그러나 생성된 프로젝트는 필요한 의존 라이브러리만 포함되었을 뿐, 로직이 구현되어 있지 않았으므로 실행 결과를 기대할 수 없습니다.

Code with Quarkus 를 이용한 프로젝트 만들기

Apache Camel 애플리케이션 프로젝트는 Code with Quarkus(https://code.quarkus.io) 사이트를 이용해 만들 수 있습니다. 이 사이트를 이용하면, 생성한 애플리케이션 프로젝트는 Apache Camel 프레임워크와 Quarkus 프레임워크를 함께 사용할 수 있습니다. Quarkus 프레임워크가 커뮤니티에 소개된 기간을 길지 않으나, Red Hat 이 Red Hat 제품에서 Spring Framework 을 대체하고, Apache Camel 을 사용하게 하는 방향으로 발전하고 있습니다.

  • CONFIGURE YOUR APPLICATION
    • Group: org.example
    • Artifact: camel-quarkus-hello
    • Build Tool: Maven
    • Extentions: Camel Core

camel-quarkus-hello

생성된 camel-quarkus-hello 프로젝트 구조는 다음과 같습니다.

camel-quarkus-hello  
├── README.md  
├── mvnw  
├── mvnw.cmd  
├── pom.xml  
└── src  
    ├── main 
    │   ├── docker 
    │   │   ├── Dockerfile.jvm 
    │   │   ├── Dockerfile.legacy-jar 
    │   │   ├── Dockerfile.native 
    │   │   └── Dockerfile.native-distroless 
    │   ├── java 
    │   │   └── org 
    │   │       └── example 
    │   │           └── GreetingResource.java 
    │   └── resources 
    │       ├── META-INF 
    │       │   └── resources 
    │       │       └── index.html 
    │       └── application.properties 
    └── test 
        └── java 
            └── org 
                └── example 
                    ├── GreetingResourceTest.java 
                    └── NativeGreetingResourceIT.java

생성된 소스 구조는 Spring Boot 와 유사합니다. 추가로 Docker 이미지 생성 관련 설정도 포함됩니다. 그런데 생성된 애플리케이션 소스는 Apache Camel 관련 의존만 포함하므로, Apache Camel 소스를 어떻게 구현할 것인가 에 대해서는 개발자가 결정해야 합니다. Quarkus 프레임워크는 개발자들에게 생소하므로, 사용하기 위한 사전 학습 또는 Red Hat 컨설팅의 도움이 필요합니다. 좀더 자세한 소개는 아래 참고 사이트를 방문해 주십시오.

생성된 Apache Camel 프로젝트는 Maven 프로젝트로 생성됐으므로, 다음 명령으로 실행할 수 있습니다.

./mvnw compile quarkus:dev

그런데 필자가 Code with Quarkus 2.7 에서 다운로드한 camel-quarkus-hello 는 컴파일이 잘 되지 않았습니다. 어떤 이유가 있을 텐데… 이런 문제는 오픈 소스를 사용하는 경우 빈번히 발생하는 문제입니다. 이 글은 Apache Camel 프로젝트를 만드는 방법을 소개하는 글이므로, 관심있는 독자께서는 직접 컴파일을 시도해 보시고, 문제가 있을 경우 문제 원인을 찾아 해결할 수 있을 것입니다.

Apache Camel Archetype 을 이용한 프로젝트 만들기

Apache Camel 은 Maven Archetype 을 이용해 Apache Camel 애플리케이션 프로젝트를 생성하는 방식도 지원합니다. 이 방식의 장점은 웹 환경이 없어도 간단히 Maven 명령을 이용해 Camel 애플리케이션 프로젝트를 생성할 수 있다는 점입니다. 다음은 명령은 Apache Camel 3.14.1 버전과 Spring Boot 프레임워크가 결합된 Apache Camel 애플리케이션 프로젝트를 생성합니다.

mvn archetype:generate \
 -B \
 -DarchetypeGroupId=org.apache.camel.archetypes \
 -DarchetypeArtifactId=camel-archetype-spring-boot \
 -DarchetypeVersion=3.14.1 \
 -DgroupId=org.example \
 -DartifactId=camel-spring-archetype-hello

Spring Boot 외 다양한 프레임워크나 기술 기반의 프로젝트를 생성할 수 있습니다. 아래 참고 사이트를 참조해 주십시오.

생성된 camel-spring-archetype-hello 프로젝트 구조는 다음과 같습니다.

camel-spring-archetype-hello  
├── pom.xml  
└── src  
 ├── main 
 │   ├── java 
 │   │   └── org 
 │   │       └── example 
 │   │           ├── MySpringBean.java 
 │   │           ├── MySpringBootApplication.java 
 │   │           └── MySpringBootRouter.java 
 │   └── resources 
 │       ├── META-INF 
 │       │   ├── LICENSE.txt 
 │       │   └── NOTICE.txt 
 │       └── application.properties 
 └── test 
     ├── java 
     │   └── org 
     │       └── example 
     │           └── MySpringBootApplicationTest.java 
     └── resources

생성된 소스는 Apache Camel Java DSL 라우트 정의는 포함되어 있으나, XML DSL 은 포함되어 있지 않습니다. XML DSL 을 포함한 애플리케이션 생성은 Red Hat Fuse 에서 만 제공하기 있습니다. 최근들어 커뮤니티에서는 Java DSL 을 선호하는 측면이 있으나, XML DSL 이 갖는 장점도 분명하므로, Apache Camel 애플리케이션은 XML DSL 과 Java DSL 을 함께 사용하는 것을 권장 드립니다. 그리고 생성된 파일 이름에 MySpring 이 붙고, 라이선스 파일이 META-INF 디렉토리 아래 위치하는데… 이런 것들을 애플리케이션 생성 후, 수정이 필요한 부분 들입니다.

Red Hat Fuse Maven Archetype 을 이용한 프로젝트 만들기

Apache Camel 프로젝트는 Red Hat 이 주도하는 오픈 소스 프로젝트입니다. 그러므로 Red Hat 은 Apache Camel 을 포함한 상용 오픈 소스 통합 제품으로 Red Hat Fuse 를 판매합니다. Red Hat Fuse 는 커뮤니티 오픈 소스인 Apache Camel 에 확장된 개발 도구, 개발 문서, 라이브러리, 레지스트리, 컨설팅 서비스, 기술 지원 서비스 등을 추가적으로 제공합니다. 그 중 Red Hat Fuse Maven Archetype 를 이용해 Apache Camel 애플리케이션 프로젝트를 만드는 방법을 제공합니다. 이 방법을 이용해 만들어진 프로젝트 소스는 커뮤니티 Apache Camel 기반이 아닌 Red Hat Camel 프로젝트가 만들어지나, 온전히 동작되는 소스 구조를 갖고 있으므로, Red Hat Fuse 를 구매한 고객의 개발자는 이 기능을 유용하게 이용할 수 있습니다. 다음은 Red Hat Fuse spring-boot-camel-xml-archetype 을 이용해 Red Hat Camel 애플리케이션을 생성한 예입니다.

wget https://gitlab.com/hinunbi/spring-boot-camel-xml-archetype/-/raw/main/configuration/settings.xml   

mvn org.apache.maven.plugins:maven-archetype-plugin:2.4:generate \
 -B \
 -s settings.xml \
 -DarchetypeCatalog=https://maven.repository.redhat.com/ga/io/fabric8/archetypes/archetypes-catalog/2.2.0.fuse-sb2-780040-redhat-00002/archetypes-catalog-2.2.0.fuse-sb2-780040-redhat-00002-archetype-catalog.xml \
 -DarchetypeGroupId=org.jboss.fuse.fis.archetypes \
 -DarchetypeArtifactId=spring-boot-camel-xml-archetype \
 -DarchetypeVersion=2.2.0.fuse-sb2-780040-redhat-00002 \
 -DgroupId=org.example \ -DartifactId=fuse-hello

위 명령 실행에 settings.xml 파일 필요한 이유는, Red Hat Fuse 애플리케이션을 생성하기 위한 Archetype 이 Apache Maven Central 저장소에는 등록되어 있지 않고, 상용 제품이다 보니, Red Hat Maven 저장소에만 등록되어 있기 때문입니다.

생성된 fuse-hello 프로젝트 구조는 다음과 같습니다.

fuse-hello/  
├── LICENSE.md  
├── configuration  
│   └── settings.xml  
├── pom.xml  
└── src  
 ├── data 
 ├── main 
 │   ├── fabric8 
 │   │   └── deployment.yml 
 │   ├── java 
 │   │   └── org 
 │   │       └── example 
 │   │           ├── Application.java 
 │   │           └── MyTransformer.java 
 │   └── resources 
 │       ├── application.properties 
 │       ├── logback.xml 
 │       └── spring 
 │           └── camel-context.xml 
 └── test 
     └── java 
         └── org 
             └── example

Red Hat Fuse Camel 애플리케이션은 개발자가 애플리케이션을 개발할 때 필요한 Spring Boot Undertow WAS 와 Camel Java DSL, XML DSL 과 최근 보안 취약점이 발견된 log4j 가 아닌 logback.xml 을 포함하고, Red Hat OpenShift 플랫폼에 컨테이너 이미지로 배포하기 위한 파일과 설정 들도 포함되어 있습니다. 단위 테스트 소스가 포함되어 있지 않은 점을 제외하면 개발자들이 개발을 시작할 때 가장 좋은 Camel 애플리케이션 소스 구조를 갖고 있습니다. 그러나 Red Hat Fuse 를 구매하지 않은 경우, 생성된 소스와 라이브러리 사용 시, 저작권 문제가 걸릴 수 있습니다.

Apache Camel 애플리케이션 만들기

Apache Camel 애플리케이션은 위와 같은 방식으로 최초 프로젝트 소스를 생성한 후, 프로젝트에 300 개 이상의 통합 컴포넌트 의존을 추가함으로, 현대 애플리케이션의 통합에 필요한 대부분의 기능을 지원할 수 있습니다. 그러나 위 생성 방법들을 이용해 생성된 Apache Camel 애플리케이션은 오픈 소스 결과물의 불완전성, 생성된 소스의 불합리한 구조, 상용 오픈 소스가 갖는 저작권 등을 때문에, 개발자는 생성된 소스를 다시 정비해 사용해야 합니다. 그리고 이렇게 정비한 소스로 Apache Camel 애플리케이션 개발 출발점을 삼아야 합니다. 그런데 이 과정은 반복되나 사람이 하는 일이라 일관되지 않는 경우가 빈번합니다. 그렇다고 계속 정비해 이용 하자니, 불편함이 따릅니다.

이런 문제점을 해결하기 위해, 필자는 위 방법을 종합한 Apache Camel 애플리케이션 Archetype 을 새로 개발했습니다. 필자가 개발한 Apache Camel Archetype 은 커뮤니티 오픈 소스에 기반한 Apache Camel 애플리케이션을 생성함에도, 소스 구조는 Red Hat Fuse 구조로 생성되고 Camel Java DSL과 XML DSL 을 모두 사용하고, Red Hat OpenShift(Kubernetes) 플랫폼에 배포될 수 있는 설정과 Maven 플러그인 등을 자동으로 포함합니다. 사용 방법은 위에서 설명한 Maven Archetype 을 이용한 Apache Camel 애플리케이션 생성과 유사합니다. 필자의 Archetype 을 사용해 Apache Camel 애플리케이션을 생성하는 명령은 다음과 같습니다. 좀더 자세한 설명은 필자의 spring-boot-camel-xml-archetype Git 저장소 를 참고해 주십시오.

git clone https://gitlab.com/hinunbi/spring-boot-camel-xml-archetype.git  
cd spring-boot-camel-xml-archetype  
git checkout tags/0.0.1
mvn install archetype:update-local-catalog  
  
mvn org.apache.maven.plugins:maven-archetype-plugin:3.2.1:generate \  
 -B \
 -DarchetypeGroupId=hinunbi.camel.archetypes \
 -DarchetypeArtifactId=spring-boot-camel-xml-archetype \
 -DarchetypeVersion=0.0.1 \
 -Dspring-boot-version=2.5.4 \
 -Dcamel-version=3.14.1 \
 -Dfabric8-version=5.12.0 \
 -Djkube-version=1.5.1 \
 -DgroupId=org.example \
 -DartifactId=camel-hinunbi-hello  

필자가 테스트한 Apache Camel 애플리케이션 생성에 사용한 Apache Camel 버전과 Spring Boot 버전 등, 이 글을 쓰는 시점에서 유효합니다.
오픈 소스들은 역동적으로 발전함으로, 이 글을 읽는 독자가 실행하는 시점에 프레임워크들의 버전이 업그레이드 된 경우, 업그레이드 버전으로 테스트 할 수 있을 것입니다. 다만 Apache Camel 이 의존하는 Spring Boot 버전에 따라 위 명령에 -Dspring-boot-version=* 값을 정확히 입력해야 점은 유의해야 합니다.

생성된 camel-hinunbi-hello 프로젝트 구조는 다음과 같습니다.

camel-hinunbi-hello/  
├── LICENSE.txt  
├── NOTICE.txt  
├── ReadMe.adoc  
├── pom.xml  
└── src  
    ├── main 
    │   ├── java 
    │   │   └── org 
    │   │       └── example 
    │   │           ├── Application.java 
    │   │           ├── MySpringBean.java 
    │   │           └── MySpringBootRouter.java 
    │   └── resources 
    │       ├── application.properties 
    │       ├── logback.xml 
    │       └── spring 
    │           └── camel-context.xml 
    └── test 
        ├── java 
        │   └── org 
        │       └── example 
        │           └── ApplicationTest.java 
        └── resources

필자가 개발한 Apache Camel Maven Archetype 을 이용해 생성한 Apache Camel 프로젝트 소스는 Red Hat Fuse Camel 애플리케이션 소스와 같은 구조이면서 순수하게 Apache Camel, Spring Boot 오픈소스로만 구성된 소스가 생성된 것을 볼 수 있습니다.

생성된 Apache Camel 프로젝트는 Spring Boot maven 프로젝트로 생성됐으므로, 다음 명령으로 실행할 수 있습니다.

mvn spring-boot:run

맺음말

일련의 애플리케이션들은 협업을 하기 위해, 좋은 아키텍처가(모노리스/마이크로서비스) 필요하듯이,
좋은 애플리케이션은 소스 구조가 좋아야 합니다.
소스 구조가 좋으려면, 소스는 사용하는 프레임워크들의 관례를 일관되게 지켜야 합니다.
그래야 개발자들은 소스 구조에 대한 공통의 이해를 갖고 개발자들 사이 의사 소통(communication)이 원할해 질 수 있습니다.
Apache Camel 애플리케이션도 이런 소스 구조 관례를 잘 지켜야 개발 생산성이 올라갈 수 있습니다.

이 글에서는 여러 프레임워크에서 Apache Camel 을 포함한 소스를 생성하는 방법과 생성된 소스 구조를 설명했습니다.
그리고 필자가 생각하는 최선의 관례를 포함한 필자의 Archetype 을 소개했습니다.
Apache Camel 에 관심을 갖고 통합 업무에 Apache Camel 프레임워크를 적용해 보려는 개발자들이 이 글을 읽고, 따라 실행해 Apache Camel 애플리케이션을 만들어 본다면,
Apache Camel 애플리케이션 개발을 시작할 때 그 출발점을 어떻게 해야할 지 결정하는데, 도움이 될 것입니다.

참고 자료

  1. Enterprise Integration Patterns
  2. 기업 통합 패턴
  3. Spring Boot Camel XML Archetype
  4. Spring Boot Initializr
  5. Code with Quarkus
  6. Apache Camel Archetype
  7. Apache Maven Archetype
  8. Quarkus + Apache Camel 소개

2020년 5월 12일 화요일

Quarkus + Apache Camel 소개

Quarkus + Apache Camel 소개

들어 가며

최근 Red Hat 은 GraalVM 기술의 등장에 발맞춰 Quarkus 오픈소스 프로젝트를 시작했습니다. 현재 GraalVM 과 함께 지속적으로 발전하는 Quarkus 는 자바 언어의 실행 방식을 확장합니다. 이 글에서는 Quarkus 를 이용해 Apache Camel 자바 애플리케이션이 어떻게 개선되는지 실험해 보고자 합니다.

Quarkus 소개

Red Hat 이 주도하는 오픈소스 프로젝트인 Quarkus 는 자바 오픈소스 및 자바 표준을 기반으로 JVM 및 native image 환경에 최적화된 애플리케이션을 개발 및 빌드할 있게 하는 Kubernetes 를 위한 자바 프레임 워크입니다. Quarkus는, Serverless, Microservice, Container, Kubernetes, FaaS 와 Cloud 를 염두에 두고 설계되어, 자바 애플리케이션을 실행하기 위한 효과적인 해결책을 제공합니다. Quarkus 는 자바 애플리케이션의 메모리 사용량을 줄여주고, 시작 시간을 빠르게 합니다. 이로써 C 나 Golang 등 native image 로 빌드되는 언어 만이 가질 수 있었던 장점을 자바 언어로 손쉽게 구현할 수 있게 합니다.

Red Hat 은 Quarkus 를 Red Hat Runtimes 에 포함해 GA 했습니다.

Apache Camel 소개

Red Hat 이 주도하는 오픈소스인 Apache Camel 은 기업 통합 패턴(EIP, Enterprise Inrgration Patters) 구현체로 경량의 통합 프레임워크입니다. 그러므로 Apache Camel 은 애플리케이션에 함께 패키징이 가능합니다. Apache Camel 은 내부에 라우터 엔진, 프로세서, 컴포넌트, 메시징 시스템을 포함해, 애플리케이션의 내부를 외부 세계와 손쉽게 인터페이스할 수 있게 해줍니다. 즉 Apache Camel 은 애플리케이션, 시스템, 서비스들 사이에서 데이터(Data)와 기능(Function)을 통합(인터페이스)하는 중재자(Mediator)로서 역할합니다.

Red Hat 은 Apache Camel 을 Red Hat Fuse 에 포함해 GA 했습니다.

Quarkus Apache Camel 프로젝트 준비

두 오픈소스 프로젝트 Quarkus 와 Apache Camel 은, Red Hat 주도로, 컨테이너 기반 MSA(Microservice Architecture)와 통합(Integration)을 손쉽게 구현할 수 있게 서로 협력하고 발전하고 있습니다. 그러므로 Quarkus Apache Camel 프로젝트의 생성도 두 기술이 함께 잘 녹아 있는 절차를 따릅니다.

이 문서의 실행 명령들은 필자의 MacBook(macOS) 에서 실행하고 검증했습니다. 그러므로 이 문서를 따라 테스트 하는 독자는 Linux, macOS, Windows 10(WSL 2 설치) 환경에서 테스트할 것을 권장드립니다.

Quarkus Apache Camel 애플리케이션도, 자바 애플리케이션이므로, 자바 애플리케이션 빌드 도구가 필요합니다.

  • JDK 11+ 과 JAVA_HOME 경로 등록
  • Apache Maven 3.6.2+

Quarkus 로 native image 를 빌드할 경우, 추가로 다음의 플랫폼 환경과 도구가 필요합니다.

  • 64 bit OS (Linux 64bit x86 or ARM, Mac OS X 64bit, Windows 64bit)
  • 시스템 메모리 14 GB+
  • GraalVM Community Edition 20.0.0 과 GRAALVM_HOME 경로 등록
  • GraalVM Native image generator (native-image)

GraalVM 은 아래 사이트에서 플랫폼에 맞는 실행 바이너리를 다운로드 합니다.

GraalVM 설치 후, GraalVM native image generator 는 다음 명령을 실행해 설치합니다.

$ cd $GRAALVM_HOME/bin
$ ./gu install native-image

주의

GraalVM 설치 후, 각 플랫폼 별로 native image 빌드를 위한 추가 개발 도구를 설치해야 합니다. 다음 사이트를 참고해 추가 개발 도구를 설치합니다.

위 사이트에 설명이 없는 CentOS 7 나 RHEL 7 플랫폼의 추가 개발 도구는 다음 명령을 실행해 설치합니다.

$ sudo yum group install -y "Development Tools"
$ sudo yum install -y zlib-devel

유스케이스

이 글의 Apache Camel 애플리케이션은 아래와 같은 통합 유스케이스의 구현을 실험했습니다. 이 통합 유스케이스는, CSV 파일을 읽어 JSON 포맷으로 Rest API 에 전달하는 절차로, 기업에서 실무적으로도 사용할 수 있을 것입니다. 실무적 유스케이스를 사용한 이유는 Apache Camel의 실무적 통합 컴포넌트와 의존 라이브러리들이 Quarkus 를 이용해 Native image 로 잘 전환되는 지를 검증하기 위해서였습니다.

  1. CVS 데이터 생산자는 5초마다 실행됩니다.
  2. 실행된 데이터 생산자는 두 줄의 CSV UserInfo 레코드를 애플리케이션 실행 디렉토리 아래 input 디렉토리에 userinfo.csv 파일로 기록합니다.
  3. 파일 소비자는 input 디렉토리에서 생성된 userinfo.csv 파일을 읽고 분할기를 이용해 두 줄의 레코드를 두 개의 UserInfo 자바 객체로 변환 (unmarshal) 합니다.
  4. 파일 소비자는 UserInfo 자바 객체의 readCount 멤버 변수 값을 파일 소비자가 읽은 순서로 갱신합니다.
  5. 파일 소비자는 웹서비스의 Rest API(http://localhost:8080/userinfo) 를 호출해 UserInfo 정보를 JSON 메시지로 전송합니다.
  6. 웹 서비스는 수신한 JSON 메시지를 콘솔 로그로 기록합니다.
  7. 콘솔 로그에서 CSV 파일 레코드 생성 시각과 파일 소비자가 읽은 순서 등을 확인합니다.

이 유스케이스의 기업 통합 패턴(EIP, Enterprise Integration Patterns) 다이어그램은 다음과 같습니다.

Quarkus Apache Camel 프로젝트

지금까지 기본 설명을 마쳤으므로, 이제 Quarkus Apache Camel 프로젝트 생성 절차를 설명합니다. Apache Camel Quarkus 프로젝트를 가장 손쉽게 시작하는 방법 중 하나는 다음 사이트에서 필요한 컴포넌트를 선택해 Qurkus 기능이 포함된 자바 프로젝트를 생성하는 것입니다.

위 사이트에 접속해 다음과 같이 생성할 프로젝트의 정보를 입력하고, 옵션을 선택합니다. 애플리케이션 상세 (Configure your application details) 에서 다음과 같이 입력합니다.

  • Group : com.demo
  • Artifact : quarkus-camel-demo
  • Build Tool : Maven

확장 선택 (Pick your extensions) 에서 다음의 확장을 선택합니다.

  • RESTEasy Jackson
  • Camel Quarkus Core
  • Camel Quarkus Timer
  • Camel Quarkus File
  • Camel Quarkus Gson
  • Camel Quarkus Bindy
  • Camel Quarkus Rest
  • Camel Quarkus Http
  • Camel Quarkus Log
  • Camel Caffeine LRUCache

"Generate your application" 버튼을 클릭해 프로젝트를 생성합니다.

다음과 같은 프로젝트 생성 화면에서 생성된 프로젝트 ZIP 파일을 다운로드합니다.

좀더 간편한 방법으로 Quarkus Code Starter 사이트에서 프로젝트를 생성하지 않고, Quarkus Maven plugin 을 이용해 다음과 같이 명령 행에서 프로젝트를 생성하는 방법도 있습니다. 이 방법을 사용하면 좀더 신속하게 프로젝트를 생성할 수 있습니다.

$ mvn io.quarkus:quarkus-maven-plugin:1.4.1.Final:create \
  -DprojectGroupId=com.demo \
  -DprojectArtifactId=quarkus-camel-demo \
  -DprojectVersion=1.0.0 \
  -DclassName="com.demo.UserInfoResource" \
  -Dpath="/userinfo" \
  -Dextensions="
    quarkus-core,
    quarkus-resteasy-jackson,
    camel-quarkus-timer,
    camel-quarkus-file,
    camel-quarkus-gson,
    camel-quarkus-bindy,
    camel-quarkus-rest,
    camel-quarkus-http,
    camel-quarkus-log,
    camel-quarkus-caffeine-lrucache"

위 과정을 마친 후, 선호하는 통합 개발 환경(IDE) 에 프로젝트를 임포트해 개발을 시작합니다.

이 과정을 통해 완성된 프로젝트와 추가된 소스를 확인하려면 필자의 소스를 참조해 주십시오.

Apache Camel DSL

위 통합 유스케이스는 Apache Camel Java DSL 로 다음과 같이 구현할 수 있습니다. (Apache Camel DSL 은 필자의 바른모 블로그Camel in Action 을 참조해 주십시오.)

package com.demo;  
  
import org.apache.camel.Exchange;  
import org.apache.camel.Processor;  
import org.apache.camel.builder.RouteBuilder;  
import org.apache.camel.dataformat.bindy.csv.BindyCsvDataFormat;  
import org.apache.camel.model.rest.RestBindingMode;  
  
import java.util.concurrent.atomic.AtomicInteger;  
  
  
public class CamelRouteBuilder extends RouteBuilder {  
  
  static AtomicInteger readCount = new AtomicInteger();  
  
  public void configure() {  
  
  BindyCsvDataFormat biindy = new BindyCsvDataFormat(com.demo.UserInfo.class);  
  
    restConfiguration()  
      .host("localhost")  
      .port(8080)  
      .jsonDataFormat("json-gson")  
      .bindingMode(RestBindingMode.json);  
  
    from("timer://csv?period=5000")  
      .routeId("csvProducerRoute")  
      .setBody(simple("Gildong,Hong,${date:now}\nWoochi,Jun,${date:now}"))  
      .to("file://input?fileName=userinfo.csv")  
      .log("New userinfo.csv saved.");  
  
    from("file://input?fileName=userinfo.csv&delete=true")  
      .routeId("csvConsumerRoute")  
      .log("Loaded userinfo.csv records\n${body}")  
      .unmarshal(biindy)  
      .split(body())  
      .parallelProcessing()  
      .process(new Processor() {  
        @Override  
        public void process(Exchange exchange) throws Exception {  
          UserInfo userInfo = exchange.getIn().getBody(UserInfo.class);  
          userInfo.setReadCount(readCount.incrementAndGet());  
        }  
     })  
     .log("Call Rest API using ${body} ...")  
     .to("rest:post:userinfo");  
  }  
}

Quarkus 는, 위와 같이 Camel DSL 클래스를 구현만 하면, 애플리케이션 시작 시 Camel DSL 자바 클래스를 자동으로 찾아 실행합니다. Spring Boot 의 경우, 애플리케이션 시작에 Camel DSL 을 실행하기 위해서는, @Service 나 @Component 애노테이션을 Camel DSL 클래스에 지정해 Spring Bean 임을 선언해야 합니다. 그런데 Quarkus 는 이런 애노테이션 지정 없이 클래스가 RouteBuilder 를 상속하면 자동으로 실행됩니다. Quarkus 의 이런 관례는, 처음 Quarkus 를 경험하는 개발자들에게 도리어 혼란을 야기할 수도 있을 것 같습니다. 관례의도 가 조화롭게 잘 정의된 프레임워크가 좋은 프레임워크이기 때문입니다.

Rest API 서비스

위 통합 유스케이스의 Rest API 서비스는 Java EE JAX-RS 표준 애노테이션을 이용해 다음과 같이 구현합니다. (참고로 Lombok 프레임워크도 Quarkus 에서 native image 로 잘 빌드되는지 실험하기 위해 일부러 사용했습니다.)

package com.demo;  
  
import lombok.extern.slf4j.Slf4j;  
  
import javax.ws.rs.Consumes;  
import javax.ws.rs.POST;  
import javax.ws.rs.Path;  
import javax.ws.rs.Produces;  
import javax.ws.rs.core.MediaType;  
  
@Slf4j  
@Path("/userinfo")  
public class UserInfoResource {  
  
  @POST  
 @Consumes(MediaType.APPLICATION_JSON)  
  @Produces(MediaType.APPLICATION_JSON)  
  public UserInfo hello(UserInfo userInfo) {  
 log.info("Received : " + userInfo);  
    return userInfo;  
  }  
}

Quarkus 가 사용하는 Java EE JAX-RS 애노테이션은, Spring Web 애노테이션과 다르지만, 가급적 표준을 따르는 것이 올바른 방법일 것 같기도 합니다. 그럼에도 Quarkus 프로젝트에서 조만간 Spring 호환 애노테이션도 제공할 것으로 보이는 데, 그렇게 되면 두 애노테이션 중 편리한 애노테이션을 사용할 수 있을 것입니다. 참고로 Quarkus 는 기본적으로 Vert.x 기술을 기반해 웹 서비스를 제공한다는 점도 알아두면 유용할 것입니다.

애플리케이션 빌드

Quarkus 를 이용하면 자바 애플리케이션을 세 가지 실행 형태로 빌드할 수 있습니다. 하나는 전통적인 Jar 실행 패키지고, 하나는 Native image 실행 바이너리입니다. 그리고 Container image 로도 빌드합니다.

Jar 빌드

Jar 빌드는 전통적인 자바 빌드 방법으로 아래와 같이 maven 명령을 이용합니다. (-DskipTests 로 테스트 단계를 건너뛰는 옵션을 지정했습니다. 이 옵션을 사용하지 않으면 테스트가 진행됩니다.)

$ ./mvnw package -DskipTests
$ ls -alh target
...
-rw-r--r--    1 jcha  staff   290K  5  5 01:38 quarkus-camel-demo-1.0.0-runner.jar
-rw-r--r--    1 jcha  staff    22K  5  5 01:38 quarkus-camel-demo-1.0.0.jar
drwxr-xr-x  129 jcha  staff   4.0K  5  5 01:38 lib
...
$ du -sh lib
 37M	lib
...

빌드가 성공하면 실행 jar 파일(*-runner.jar), 클래스를 포함한 jar, 그리고 의존 라이브러리를 포함하는 lib 디렉토리가 생성됩니다. 생성된 실행 jar 파일의 크기는 290 KB 크기로, 실제 소스가 컴파일된 jar 22 KB 와 Quarkus 클래스 268 KB 가 포함됩니다. Quarkus 클래스 영역은 애플리케이션이 커져도 동일한 크기를 가질 것입니다. Quarkus 클래스들은 실행 jar 를 실행하면 실제 애플리케이션을 탑재하고 시작시킵니다. 실행은 아래 Jar 패키지 실행을 참조해 주십시오.

Native image 빌드

Native image 빌드는 자바 애플리케이션이 JVM 의존 없는 단일 실행 바이너러로 빌드되는 것을 의미합니다. 즉 단일 실행 바이너리가 JVM 기능을 포함합니다. 빌드 방법은 아래와 같이 Maven 을 이용해 빌드합니다. Jar 빌드와 다른 점은 Maven 빌드에 native 프로파일 옵션인 -Pnative 을 추가로 지정했다는 점입니다. Native image 빌드에 사용하는 Maven native 프로파일은 Quarkus Code Starter 나 Quarkus maven plugin 이 프로젝트 생성할 때 Maven pom.xml 에 자동으로 구성합니다.

$ ./mvnw package -Pnative -DskipTests
...
[quarkus-camel-demo-1.0.0-runner:6453]      [total]: 147,334.21 ms, 11.75 GB
...
$ ls -alh target
drwxr-xr-x    4 jcha  staff   128B  5  5 10:29 quarkus-camel-demo-1.0.0-native-image-source-jar
-rwxr-xr-x    1 jcha  staff    87M  5  5 10:29 quarkus-camel-demo-1.0.0-runner
...
$ file target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner: Mach-O 64-bit executable x86_64

$ otool -L target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner:
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 902.1.0)
    /System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices (compatibility version 1.0.0, current version 1069.22.0)
    /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 1675.129.0)
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1281.100.1)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)

Native image 빌드는 상당하 많은 시스템 메모리를 요구합니다. 필자의 개발 PC(MacBook) 빌드에서는 최종적으로 11.75 GB의 시스템 메모리가 사용됐다고 기록이 남았습니다. GraalVM native image 문서를 보면 Native image 빌드를 위해서는 시스템 메모리가 14 GB 이상이 필요하다고 합니다. GraalVM 팀은 빌드 과정 메모리 최적화를 안한 것인지 못하는 것인지 궁금합니다. (이런 이유로 필자의 라즈베리 파이에서는 빌드 환경은 구성 가능했지만 직접적으로 Native image 빌드는 수행할 수 없었습니다. )

빌드가 성공하면 실행 바이너리 파일(*-runner)과 자바 애플리케이션 클래스를 포함한 jar 파일이 생성됩니다. Native image 빌드로 생성된 실행 바이너리는 87 MB 크기로 JVM 기능을 포함합니다. 실행 바이너리는 jar 패키지, 의존 jar, 그리고 JVM 영역을 모두 포함하므로, 애플리케이션 클래스 jar 와 lib 37 MB 을 빼면, 실행 바이너리에서 순수 JVM 영역은 약 50 MB 정도로 볼 수 있을 것입니다.

실행 바이너리 실행 시, 모든 실행 바이너리가 실행 메모리에 적재되지는 않으므로, 실제 시스템 메모리 사용은 실행 바이너리 크기보다 훨씬 적습니다.

필자는 개발 PC 는 MacBook 이므로, 생성된 실행 바리너리를 file 명령을 확인해 본 결과, 파일 형식이 Mach-O 64-bit executable x86_64 인 Native image 파일(*-runner)임을 확인한 수 있었습니다.

CentOS 7 에서도 빌드를 해 보았는데, 이 경우 실행 바이너리의 의존은 다음과 같았습니다.

$ ldd target/quarkus-camel-demo-1.0.0-runner
        linux-vdso.so.1 =>  (0x00007ffdc6f93000)
        libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007faa18c05000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007faa189e9000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007faa187e5000)
        libz.so.1 => /lib64/libz.so.1 (0x00007faa185cf000)
        librt.so.1 => /lib64/librt.so.1 (0x00007faa183c7000)
        libc.so.6 => /lib64/libc.so.6 (0x00007faa17ff9000)
        libm.so.6 => /lib64/libm.so.6 (0x00007faa17cf7000)
        /lib64/ld-linux-x86-64.so.2 (0x00007faa18f0c000)
        libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007faa17ae1000) 

위 결과로 Native image 로 빌드된 자바 애플리케이션은 플랫폼의 기본 라이브러리들에 의존하고 있음을 알 수 있었습니다. 그런데 만약 자바 애플리케이션의 Native image 영역 중 반드시 최적화가 필요하지 않은 JVM 영역이 플랫폼에 맞도록 공유 라이브러리로 분리될 수 있다면, 자바 애플리케이션의 Native image 크기를 더 줄일 수 있지 않을까 생각해 봅니다.

Container image 빌드

Container image 빌드는 Native image 빌드를 실행한 플랫폼 OS 의 실행 바이너리가 아닌 컨테이너 실행 환경인 Linux 의 실행 바이너리를 생성한다는 점이 다릅니다.
필자는 필자의 개발 PC 에서 Container image 빌드를 하기 위해 Mac 용 “Docker desktop” 을 설치했습니다. 다음은 필자가 사용한 “Docker desktop” 버전은 다음과 같습니다.

Container image 빌드는 Native image 빌드에 -Dquarkus.native.container-build=true 옵션을 추가하면 됩니다.

$ ./mvnw package -Pnative -Dquarkus.native.container-build=true -DskipTests
...
[quarkus-camel-demo-1.0.0-runner:24]      [total]: 181,112.75 ms
[INFO] [io.quarkus.deployment.QuarkusAugmentor] Quarkus augmentation completed in 191029ms
...
$ ls -alh target
drwxr-xr-x    4 jcha  staff   128B  5  7 18:57 quarkus-camel-demo-1.0.0-native-image-source-jar
-rwxr-xr-x    1 jcha  staff    89M  5  7 18:57 quarkus-camel-demo-1.0.0-runner
...
$ file target/quarkus-camel-demo-1.0.0-runner
target/quarkus-camel-demo-1.0.0-runner: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b951368d112bf5a6589d0c6fe8e67712cd9ce87f, with debug_info, not stripped

$ otool -L target/quarkus-camel-demo-1.0.0-runner
llvm-objdump: error: target/quarkus-camel-demo-1.0.0-runner': object is not a Mach-O file type.

위 콘솔 로그에서 보듯이 필자의 개발 PC 에서는 Conatiner image 빌드가 약 3분 정도 소요됐습니다. 그리고 필자의 개발 PC 가 MacBook(macOS) 임에도 불구하고, 생성된 실행 바이너리(*-runner)의 파일 형식은 ELF 64-bit LSB executable 로 컨테이너 이미지에 포함할 수 있는 Linux 용 바이너리가 생성됐습니다. 기대한 대로 생성된 실행 바이너리는 macOS 의 otool 로도 읽을 수 없습니다.

생성된 컨테이너 용 실행 바이너리는 다음 절차를 수행해 최종적으로 컨테이너 이미지로 빌드합니다.

$ docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus-camel-demo .
...
Successfully built 7a6b92b9eaa3
Successfully tagged quarkus/quarkus-camel-demo:latest
$ docker images                                                                    
REPOSITORY                                                           TAG                 IMAGE ID            CREATED              SIZE
quarkus/quarkus-camel-demo                                           latest              7a6b92b9eaa3        About a minute ago   294MB
registry.access.redhat.com/ubi8/ubi-minimal                          8.1                 91d23a64fdf2        5 weeks ago         107MB

...

최종적으로 생성된 자바 애플리케이션 컨테이너 이미지는 294MB 크기입니다. 이 컨테이너 이미지의 베이스 이미지인 레드햇 유니버셜 베이스 이미지(ubi-minimal:8.1)는 107 MB 입니다.

애플리케이션 실행

이 글에서 자바 애플리케이션은 세 가지 형태로 빌드됐습니다. 하나는 전통적인 Jar 패키지이고 다른 하나는 Native image 이고, 마지막으로 Container image 입니다. 각각의 실행 방법은 다음과 같습니다.

Jar 애플리케이션 실행

다음은 필자의 개발 PC 에서 빌드된 jar 실행 패키지 자바 애플리케이션을 실행한 결과입니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 01:38:39,595 INFO  [org.apa.cam.sup.LRUCacheFactory] (main) Detected and using LURCacheFactory: camel-caffeine-lrucache
2020-05-05 01:38:39,800 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-05 01:38:39,802 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 01:38:39,889 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 01:38:40,056 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@6724cdec
2020-05-05 01:38:40,115 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 01:38:40,120 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 01:38:40,125 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 01:38:40,127 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.325 seconds
...

Quakus 로 빌드한 Jar 실행 패키지 자바 애플리케이션의 시작 시간은 0.325 초였습니다. 일반적으로 자바 애플리케이션의 시작은 JVM 의 실행 초기화와 프레임워크의 초기화를 포함합니다. 그러므로 최소한 수 초 정도 시작 시간이 필요합니다. 그런데 이 과정도 Qurkus 는 최적해 실행 패키지의 시작 시간이 개선됐음을 볼 수 있었습니다.

Native image 애플리케이션 실행

다음은 필자의 개발 PC 에서 Native image 자바 애플리케이션을 실행한 결과입니다.

$ ./target/quarkus-camel-demo-1.0.0-runner              
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 12:51:21,838 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-05 12:51:21,838 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 12:51:21,842 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 12:51:21,844 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@1127f7828
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 12:51:21,851 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.013 seconds
...

애플리케이션 실행 결과, Native image 형식으로 빌드한 자바 애플리케이션의 시작 시간은 0.013 초였습니다. 즉 Native image 자바 애플리케이션은 약 13 밀리초 만에 시작을 완료했습니다. 일반적인 자바 애플리케이션은 시작 시간이 수 초 이상입니다. 이 결과로 Native image 로 빌드된 자바 애플리케이션은 기존의 자바 애플리케이션보다 약 100 배 시작 시간이 빨라짐을 확인할 수 있었습니다.

애플리케이션의 실행 시간 (및 종료 시간) 이 빨라지면 신속한 배포가 가능하고, 배포에 따른 중단 시간을 단축할 수 있습니다. 이런 기술적 특징은 Serverless 등 컨테이너 기술에 필수적 조건입니다.

Container image 애플리케이션 실행

다음은 필자의 개발 PC 에서 빌드된 자바 애플리케이션 컨테이너 이미지를 실행한 결과입니다.

$ docker run -i --rm -p 8080:8080 quarkus/quarkus-camel-demo
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-07 10:19:24,690 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) is starting
2020-05-07 10:19:24,690 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-07 10:19:24,692 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-07 10:19:24,693 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@7f9091eb2868
2020-05-07 10:19:24,699 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-07 10:19:24,699 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-07 10:19:24,700 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-07 10:19:24,701 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: camel-1) started in 0.010 seconds
2020-05-07 10:19:24,707 INFO  [io.quarkus] (main) =quarkus-camel-demo 1.0.0 (powered by Quarkus 1.4.1.Final) started in 0.027s. 
...

Container image 실행 결과도 Native image 형식으로 빌드한 자바 애플리케이션의 시작 시간은 0.013 초과 유사하게 0.027 초 였습니다. 즉 Container image 자바 애플리케이션은 약 27 밀리초 만에 시작을 완료했습니다. Native image 를 컨테이너 이미지로 빌드하면서 컨테이너 시작 단계가 포함돼 순수 Native image 시작 시간보다 길어지긴 했지만, Quarkus 를 이용해 Native image 을 Container image 로 전환한 자바 애플리케이션은 초 당 40 개 정도 실행 할 수 있다는 가능성을 확인할 수 있었습니다.

애플리케이션 실행 시간 비교

Quarkus 를 이용한 자바 애플리케이션 빌드 실행 시간 실험은 다음과 다음 결과로 요약됩니다.

빌드 결과물 시작 시간 설명
Jar 패키지 0.325 초 초당 3 개 애플리케이션 실행 가능
Native image 0.013 초 초당 80 개 애플리케이션 실행 가능
Container image 0.027 초 초당 40 개 애플리케이션 실행 가능

Qurakus 프로젝트 웹 사이트 설명

Quarkus 프로젝트에서 설명한 응답 시간 자료도 이번 실험 결과와 유사한 결과를 설명하고 있었습니다.

애플리케이션 메모리

Jar 애플리케이션

애플리케이션 메모리 상태를 확인하기 위해 Quarkus 를 이용해 빌드한 Jar 애플리케이션의 실행합니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar
...

다른 실행 창에서 Jar 애플리케이션 메모리 사용을 확인합니다.

$ ps x -o pid,rss,vsz,command | grep quarkus-camel-demo-1.0.0-runner.jar
28338 342832 25454416 /usr/bin/java -jar target/quarkus-camel-demo-1.0.0-runner.jar

상주 메모리는 약 334 MB, 가상 메모리는 약 24 GB 정도 사용하는 것으로 표시됐습니다.

아래는 필자의 개발 PC 자원 모니터링 화면에서 본 Jar 애플리케이션의 메모리 사용 현황입니다.

위 결과는 271.9 MB 로 표시됐습니다.

Native image 애플리케이션

애플리케이션 메모리 상태를 확인하기 위해 Quarkus 를 이용해 생성한 생성한 Native image 애플리케이션을 실행합니다.

$ target/quarkus-camel-demo-1.0.0-runner
...

다른 실행 창에서 Native image 애플리케이션 메모리 사용을 확인합니다.

$ ps x -o pid,rss,vsz,command | grep quarkus-camel-demo-1.0.0-runner
28521  51016  4746796  target/quarkus-camel-demo-1.0.0-runner

상주 메모리는 약 51 MB, 가상 메모리는 약 4.6 GB 정도 사용하는 것으로 표시됐습니다.

아래는 필자의 개발 PC 자원 모니터링 화면에서 본 Jar 애플리케이션의 메모리 사용 현황입니다.

위 결과는 31.3 MB 로 표시됐습니다.

Native image 자바 애플리케이션은 Jar 애플리케이션보다 9 배 정도 적은 메모리로 구동되는 것을 확인 할 수 있었습니다. 그러므로 동일한 기능을 하는 애플리케이션을 Native image 로 빌드해 실행하면 동일 장비에 9 배 이상 밀집도를 갖고 실행 할 수 있으므로, Native image 빌드는 시스템 자원 효율을 9 배 이상 향상시킨다고 볼 수 있습니다.

애플리케이션 실행 메모리 비교

Quarkus 를 이용한 자바 애플리케이 빌드 메모리 사용은 실험은 다음과 다음 결과로 요약됩니다.

빌드 결과물 사용 메모리 설명
Jar 패키지 334 MB 일반적인 자바 애플리케이션 사용 메모리
Native image 51 MB 6 배 절감된 메모리 사용

Qurakus 프로젝트 웹 사이트 설명

Quarkus 프로젝트에서 설명한 메모리 자료도 이번 실험 결과와 유사한 결과를 설명하고 있었습니다.

라즈베리 파이(Raspberry PI)

자바 애플리케이션을 적은 메모리로 빠르게 실행할 수 있다면 , 라즈베리 파이 같은 사물 인터넷(IOT) 장비의 언어로 자바 언어가 상당히 매력적인 언어가 될 수 있을 것입니다. 필자는 이런 생각을 기반으로 Quarkus 개발 환경을 라즈베리 파이에 구성해 동일한 애플리케이션을 빌드해 시도해 보았습니다. 그러면서 아래와 같은 문제를 만났습니다.

  • 라즈베리 파이 32bit OS
  • ARM 프로세서
  • 시스템 메모리 한계 (최대 4 GB)

위 문제 중 32bit OS 문제는 라즈베리파이 용 Ububtu 64bit OS 를 설치해서 해결했고, ARM 프로세서 문제는 ARM 기반 Open JDK 11, GraalVM 을 설치해 해결했습니다. 그러나 시스템 메모리 부족 문제로 Native image 빌드는 라즈베리 파이 내에서 완료할 수 없었습니다. (그 이유는 Native image 빌드를 위해서는 14 G 이상의 시스템 메모리가 필요하기 때문입니다.) 그러나 ARM 기반 서버도 최근에는 쉽게 구할 수 있으므로 메모리가 충분한 ARM 기반 서버에서 Native image 를 생성해 라즈베리파이에서 실행하면 되므로 굳이 라즈베리 파이에서 Native image 빌드까지 하지 않더라도 활용하는 대는 문제가 없을 것으로 보입니다. 그러므로 자바 언어는 라즈베리 파이와 같은 IOT 영역에서도 곧 큰 힘을 발휘할 때가 올 것으로 전망합니다.

다음은 라즈베리 파이에서 Quarkus 로 통합 애플리케이션을 빌드한 결과입니다.

$ ls -alh target/
...
-rw-rw-r--  1 ubuntu ubuntu 278K  5월  4 13:25 quarkus-camel-demo-1.0.0-runner.jar
-rw-rw-r--  1 ubuntu ubuntu 8.9K  5월  4 14:12 quarkus-camel-demo-1.0.0.jar
drwxrwxr-x  2 ubuntu ubuntu  16K  5월  4 13:25 lib
...
$ du -sh target/lib
37M     target/lib

Quarkus 로 Jar 실행 패키지 빌드 결과는 macOS 나 CentOS 나 크게 다르지 않았습니다. 이 결과는 자바 언어가 플랫폼 독립적이라는 점을 잘 보이고 있는 것입니다.

다음은 라즈베리 파이에서 Jar 패키지 통합 애플리케이션을 실행한 결과입니다.

$ java -jar target/quarkus-camel-demo-1.0.0-runner.jar 
__  ____  __  _____   ___  __ ____  ______ 
 --/ __ \/ / / / _ | / _ \/ //_/ / / / __/ 
 -/ /_/ / /_/ / __ |/ , _/ ,< / /_/ /\ \   
--\___\_\____/_/ |_/_/|_/_/|_|\____/___/   
2020-05-05 01:32:44,508 INFO  [org.apa.cam.sup.LRUCacheFactory] (main) Detected and using LURCacheFactory: camel-caffeine-lrucache
2020-05-05 01:32:49,434 INFO  [org.apa.cam.mai.BaseMainSupport] (main) Auto-configuration summary:
2020-05-05 01:32:49,438 INFO  [org.apa.cam.mai.BaseMainSupport] (main)  camel.context.name=quarkus-camel-demo
2020-05-05 01:32:49,669 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: quarkus-camel-demo) is starting
2020-05-05 01:32:49,682 INFO  [org.apa.cam.imp.eng.DefaultManagementStrategy] (main) JMX is disabled
2020-05-05 01:32:50,590 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) StreamCaching is not in use. If using streams then its recommended to enable stream caching. See more details at http://camel.apache.org/stream-caching.html
2020-05-05 01:32:52,444 INFO  [org.apa.cam.com.htt.HttpComponent] (main) Created ClientConnectionManager org.apache.http.impl.conn.PoolingHttpClientConnectionManager@2436ea2f
2020-05-05 01:32:53,352 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvProducerRoute started and consuming from: timer://csv
2020-05-05 01:32:53,390 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Route: csvConsumerRoute started and consuming from: file://input
2020-05-05 01:32:53,416 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Total 2 routes, of which 2 are started
2020-05-05 01:32:53,428 INFO  [org.apa.cam.imp.eng.AbstractCamelContext] (main) Apache Camel 3.1.0 (CamelContext: quarkus-camel-demo) started in 3.749 seconds
...

라즈베리 파이에서 애플리케이션의 시작 시간은 3.749 초가 걸렸습다. 개발 PC 의 시작 시간 0.325 초에 비해 길지만 라즈베리 파이 환경에서 4초 이내 자바 애플리케이션이 완전히 실행됐다는 점에서 의미있는 시작 시간으로 볼 수 있습니다.
필자는 별도의 ARM 서버를 준비하지 못해, 라즈베리 파이에서 Native image 애플리케이션을 실행해 보지 못했지만, Native image 자바 애플리케이션을 라즈베리 파이에서 실행한다면, 시작 시간은 1초 이내고 메모리도 수십 MB 로 실행될 것을 예상할 수 있을 것입니다. 이 정도 실행 속도와 자원 사용 환경이라면, IOT 에서도 충분히 의미있는 애플리케이션을 개발, 운영할 수 있을 것입니다. 즉 자바 언어가 만든 유용한 라이브러리와 프레임워크 등을 IOT 영역에 도입하는데 큰 장애물이 사라졌다고 볼 수 있을 것입니다.

참고로 ARM 서버는 Amazon EC2 A1 인스턴스 에서 저렴하게 임대할 수 있습니다. 라즈베리 파이에서 Native image 자바 애플리케이션 실행의 검증은 관심있는 독자에게 맡깁니다.

맺음말

GraalVM 이 등장하면서, 자바 컴파일 방식에 큰 발전이 일어났습니다. 지금까지 자바 컴파일러가 바이트 코드를 생성했다면, GraalVM 컴파일러는 바이트 코드 뿐만 아니라, native image 를 생성할 수도 있게 됐습니다. 이에 따라 native image 자바 애플리케이션은 C 나 Golang 언어만큼 시작 속도와 메모리 사용이 빨라지고 작아지게 됐습니다.

지금까지 자바 언어는 JVM 에 의존하면서 서버 애플리케이션이으로서 탄탄한 상태계를 구축했지만, 컨테이너 시대가 도래하고, 새로운 언어들이 등장하고 발전하는 데, 느린 시작 시간과 많은 메모리 사용으로 유용성이 제한되는 문제가 있었습니다. 그러나 이제는 자바 언어가 native image 로 동작 가능하게 되면서, 컨테이너 시대에도 빠른 시작 시간과 적은 메모리 사용으로, 주력 언어의 지위를 계속 이어 갈 수 있게 됐습니다.

Quarkus 는 자바 언어를 컨테이너 시대에 사용하기 편리하게 해주는 프레임워크입니다. Spring Boot 가 웹 서버를 내장하면서 WAS(Web Application Server) 를 벗어나 자바 MSA(Microservice Architecture) 에 주력 프레임워크가 됐듯이, Quarkus 는 현재도 여전히 발전하는 프로레임워크로 Spring Boot 와 유사한 기능을 제공하고, native image 빌드를 위한 확장을 제공하므로, 컨테이너 MSA 환경에서 중요한 프레임워크가 될 것으로 기대해 봅니다.

Apache Camel 은 EIP(Enterprise Integration Patters) 의 구현체인 통합 프레임워크로 Spring Boot 뿐만 아니라 Quarkus 와도 잘 결합돼 컨테이너 MSA 환경에서 레거시 통합 및 MSA 통합에 주요한 프레임워크입니다. 더불어 Qurakus 기술과 결합해 MSA 통합에서 IOT 통합까지 다양한 응용 분야에서 중요한 기술로서 가능성이 있습니다.

필자가 GraalVM 기술을 처음 접했을 때, 왜 이 기술이 나올 때까지 20년 (자바 언어는 1995년 탄생) 이상이 걸렸는지 의아하기도 하고 또 반가웠습니다. 이제 자바로 Golang 처럼 빠른 실행과 적은 메모리를 사용하는 애플리케이션을 개발할 수 있다는 점은 자바 언어가 앞으로 10년 이상 건재할 것이라는 것을 의미하기 때문입니다.

GraalVM, Quarkus, Apache Camel, 그리고 Spring Boot 도 자바를 native image 세계로 인도하고 있습니다. 물론 아직은 완전히 성숙됐다고 볼 수 없어 실제 기술 검토를 하면, 여러 가지 문제를 만나게 됩니다. 그럼에도 지속적으로 발전하고 편해지고 있습니다. 그 중 Red Hat 은 Quarkus 와 Apache Camel 을 서브스크립션을 통해 기술 지원하므로 커뮤니티 오픈소스를 직접 사용함에 따른 다양한 시행착오와 기술 문제의 해결을 지원 받을 수 있다는 점은, 이 기술에 관심을 가진 기업이 비즈니스 애플리케이션에 이용하는 데 장벽을 낮춰줄 수 있을 것입니다. 이 글을 읽는 독자가 이 새로운 변화에 관심을 갖는다면 분명 이 시대 누구보다 앞선 엔지니어가 될 수 있을 것입니다.

사마천 사기 상군(商君) 열전 에 이런 말이 나옵니다.

기구일지라도 그 효용이 10 배가 되는 것이 아니면 바꾸지 않는 법이다.

끝으로 GraalVM 과 Qurakus 그리고 Apache Camel 의 결합은 10 배 이상의 효용을 줄 수 있을 것이라 감히 이야기 해봅니다.

참고 자료

  1. Quarkus Apache Camel Demo Source
  2. GraalVM
  3. GraalVM Downloads
  4. Quarkus
  5. Red Hat Qurakus
  6. Quarkus Getting Started
  7. Quarkus Code Starter
  8. Apache Camel
  9. Red Hat Fuse
  10. Camel in Action
  11. 기업 통합 패턴(Enterprise Integration Patterns)
  12. 바른모 블로그
  13. Spring Boot
  14. CentOS
  15. Raspberry PI
  16. Ubnutu server on Raspberry PI
  17. Docker desktop
  18. Windows 10 WSL2 설치 지침
  19. Amazon EC2 A1 인스턴스

2018년 3월 1일 목요일

Red Hat OCP 3.5 DNS 분석

Red Hat OCP 3.5 DNS 분석

이 문서는 OpenShift Cluster Platform 3.5 설치로 구성되는 OpenShift Cluster 의 DNS 구조를 분석합니다.

OCP 클러스터 서버 구성도

설명에 사용되는 OCP 클러스터 서버 구성도는 아래와 같습니다.

OCP 클러스터 네트워크 구성도

설명에 사용되는 OCP 클러스터의 네트워크 구성은 아래와 같습니다.

OCP DNS 메커니즘 분석

설치 전

master1 노드

/etc/resolv.conf

# Generated by NetworkManager 	
search 		example.com
nameserver 192.168.181.5 

eth0 정보

['ipv4' 설정 값]  
ipv4.method: manual  
ipv4.dns: 192.168.181.5  
ipv4.dns-search: example.com  
ipv4.dns-options: (기본값)  
ipv4.dns-priority: 0  
ipv4.addresses: 192.168.181.11/24  
ipv4.gateway: 192.168.181.2  
ipv4.routes:  
ipv4.route-metric: -1  
ipv4.ignore-auto-routes: no  
ipv4.ignore-auto-dns: no  
ipv4.dhcp-client-id: --  
ipv4.dhcp-timeout: 0  
ipv4.dhcp-send-hostname: yes  
ipv4.dhcp-hostname: --  
ipv4.dhcp-fqdn: --  
ipv4.never-default: no  
ipv4.may-fail: yes  
ipv4.dad-timeout: -1(기본값)

node1 노드

/etc/resolv.conf

# Generated by NetworkManager
search example.com
nameserver 192.168.181.5

eth0 정보

['ipv4' 설정 값]  
ipv4.method: manual  
ipv4.dns: 192.168.181.5  
ipv4.dns-search: example.com  
ipv4.dns-options: (기본값)  
ipv4.dns-priority: 0  
ipv4.addresses: 192.168.181.21/24  
ipv4.gateway: 192.168.181.2  
ipv4.routes:  
ipv4.route-metric: -1  
ipv4.ignore-auto-routes: no  
ipv4.ignore-auto-dns: no  
ipv4.dhcp-client-id: --  
ipv4.dhcp-timeout: 0  
ipv4.dhcp-send-hostname: yes  
ipv4.dhcp-hostname: --  
ipv4.dhcp-fqdn: --  
ipv4.never-default: no  
ipv4.may-fail: yes  
ipv4.dad-timeout: -1(기본값)

설치 후

master1 노드 분석

OCP 설치 중 발생되는 변경

  • OCP 설치 후 master1 노드에 dnsmasq DNS 서버(53)와 skyDNS DNS 서버(8053)가 실행 됨
  • OCP 설치 후 resolv.conf 정보가 변경됨
  • OCP 설치 후 원래 nameserver “192.168.181.5”는 마스터 노드 자신의 IP로 변경됨, OCP가 설치 중 마스터 서버에 강제로 dnsmasq DNS 서버를 설치하기 때문임.
  • OCP 설치 후 NetworkManager가 기동하는 99-origin-dns.sh 파일이 /etc/NetworkManager/dispatcher.d/에 추가됨
  • 원래 마스터 서버의 nameserver 정보가 이동되는 위치는 아래 문서 참조

/etc/resolv.conf

# Generated by NetworkManager
search example.com
nameserver 192.168.181.21
\# nameserver updated by /etc/NetworkManager/dispatcher.d/99-origin-dns.sh

/etc/dnsmasq.conf

…
# Include another lot of configuration options.
#conf-file=/etc/dnsmasq.more.conf
conf-dir=/etc/dnsmasq.d
  • OCP 설치 후 dnsmasq 서비스가 참조하는 아래 파일이 생성됨

/etc/dnsmasq.d/origin-dns.conf

no-resolv
domain-needed
server=/cluster.local/172.30.0.1
  • no-resolv: OCP DNS 메커니즘에서는 node 서버의 resolv.conf 는 사용하지 않음
  • domain-needed: 도메인 이름 없는 DNS 질의는 허용하지 않음
  • server=/cluster.local/172.30.0.1 : cluster.local 의 와일드 카드 주소는 172.30.0.1 임

Master 서버에서 OCP 설치 전 사용된 DNS 주소는 OCP 설치 후 2차 DNS 질의 주소가 됨

/etc/dnsmasq.d/origin-upstream-dns.conf

server=192.168.181.5

node1 노드 분석

  • node1 서버에는 dnsmasq DNS 서버(53)만 실행 됨
  • resolv.conf 정보가 변경됨
  • OCP 설치 후 원래 node 서버의 nameserver “192.168.181.5”는 노드 서버 자신의 IP로 변경됨, OCP가 설치 중 노드 서버에 강제로 dnsmasq DNS 서버를 설치하기 때문임.
  • OCP 설치 후 NetworkManager가 기동하는 99-origin-dns.sh 파일이 /etc/NetworkManager/dispatcher.d/에 추가됨
  • 원래 노드 서버의 nameserver 정보가 이동되는 위치는 아래 문서 참조

/etc/resolv.conf

# Generated by NetworkManager
search example.com
nameserver 192.168.181.21
# nameserver updated by /etc/NetworkManager/dispatcher.d/99-origin-dns.sh

/etc/dnsmasq.conf

…
# Include another lot of configuration options.
#conf-file=/etc/dnsmasq.more.conf
conf-dir=/etc/dnsmasq.d
  • OCP 설치 후 dnsmasq 서비스가 참조하는 아래 파일이 생성됨

/etc/dnsmasq.d/origin-dns.conf

no-resolv
domain-needed
server=/cluster.local/172.30.0.1
  • no-resolv: OCP DNS 메커니즘에서는 node 서버의 resolv.conf 는 사용하지 않음
  • domain-needed: 도메인 이름 없는 DNS 질의는 허용하지 않음
  • server=/cluster.local/172.30.0.1 : cluster.local 의 와일드 카드 주소는 172.30.0.1 임

  • Master 서버에서 OCP 설치 전 사용된 DNS 주소는 OCP 설치 후에는 2차 DNS 질의 주소가 됨

/etc/dnsmasq.d/origin-upstream-dns.conf

server=192.168.181.5

POD DNS 분석

OCP 설치 후 lab7 프로젝트에 실성한 pod의 resolv.conf 는 다음과 같습니다.

/etc/resolv.conf

search lab7.svc.cluster.local svc.cluster.local cluster.local  example.com
nameserver 192.168.181.21
nameserver 192.168.181.21  
options ndots:5
  • 첫번째 nameserver는 /etc/origin/node/node-config.yaml 파일에 지정된 dnsIP의 값에서 유래합니다. node-config.yaml는 OCP 설치 과정에서 생성되는 것 같습니다.
  • 두번째 nameserver는 node 서버의 resolv.conf에서 유래합니다. 그런데 여기서 문제는 OCP 설치 전 노드 서버의 resolv.conf 에서 유래하는 것이 아니라, OCP 설치 후 변경된 노드 서버의 resolv.conf에서 유래함으로 첫번째 nameserver와 동일한 IP 주소를 갖는다는 점입니다. 합리적으로 OCP 설치 전 노드 서버 nameserver 정보가 입력돼야 할 것인데, OCP 설치 과정에서 변형한 nameserver 정보가 다시 입력된다는 것은 논리적으로 이상합니다. 노드 서버의 dnsmasq 도 OCP가 설치한 서비스이고 DNS 이름 검색에도 참여 합니다.
  • lab7.svc.cluster.local 는 pod 가 실행되는 프로젝트에서 유래합니다. 이 예는 lab7 프로젝트 내 생성한 pod 이므로 search 항목에 lab7.svc.cluster.local 가 포함되었습니다.
  • svc.cluster.local cluster.local 는 node-config.yaml의 cluster.local 에 따라 자동 추가되는 domain name 항목입니다.
  • example.com 은 node 서버의 resolve.conf에서 유래한 domain name 항목입니다. 이 항목은 사설 IP의 기본 domain 이름으로 활용됩니다.
  • OpenShift Container Platform 3.5 Installation and Configuration page 17을 참조하여 주십시오.

내부 Service IP 주소 얻기

  1. Pod 내 애플리케이션이 “registry-console.default” 이름의 IP 주소를 시스템 콜을 이용해 요청한다고 가정합니다. 이 경우 registry-console 는 Pod 이름이고 default는 프로젝트 이름입니다.

  2. Pod 의 resolv.conf 에 등록되는 정보는 다음과 같습니다.

    1. 192.168.181.21(1차 nameserver) : Node 서버
    2. /etc/origin/node/node-conf.yaml 의 dnsIP 에서 유래
    3. 192.168.181.21(2차 nameserver) : Node 서버 /etc/resolv.conf 의 namesever 에서 유래
    4. 첫번째 search : pod project name + svc + Node 서버 /etc/origin/node/node-conf.yaml 의 dnsDomain (cluster.local) 에서 유래
    5. 두번째 search: svc + Node 서버 /etc/origin/node/node-conf.yaml 의 dnsDomain (cluster.local) 에서 유래
    6. 세번째 search: Node 서버 /etc/origin/node/node-conf.yaml 의 dnsDomain (cluster.local) 에서 유래
    7. 네번째 search: Node 서버 /etc/resolv.conf 의 search (example.com) 에서 유래
  3. Pod 시스템은 애플리케이션에서 요청 받은 DNS 질의(registry-console.default)를 Pod 의 resolv.conf search 설정에 따라 다음 이름으로 DNS 이름을 조합하여 Node 서버(192.168.181.21)의 dnsmasq 에게 아래 목록의 DNS 이름들을 질의합니다.

    1. registry-console.default.lab7.svc.cluster.local
    2. registry-console.default.svc.cluster.local
    3. registry-console.default.cluster.local
    4. registry-console.default.example.com
  4. Node 서버(192.168.181.21)의 dnsmasq는 캐시에 없는 경우 cluster.local 로 종료되는 DNS 질의는 dnsmasq.conf의 와일드카드가 cluster.local 을 가리키는 kubernetes(172.30.0.1) 서비스로 다시 질의합니다.

  5. kubernetes(172.30.0.1:53) 서비스는 SkyDNS 서버(192.168.181.11:8053)로 패킷을 포워딩합니다.

  6. SkyDNS 서버(192.168.181.11:8053)는 kubernetes(172.30.0.1) 서비스에게 질의들 중 부합하는 “registry-console.default.svc.cluster.local” 의 IP 주소 172.30.145.119 를 응답합니다.

  7. kubernetes(172.30.0.1) 서비스는 Pod(192.168.181.21) dnsmasq DNS 서버에게 결과를 반환합니다.

  8. Pod(192.168.181.21) dnsmasq DNS 서버는 Pod 애플리케이션에게 “registry-console.default” 의 응답 IP 주소 172.30.145.119를 반환합니다.

  9. OCP 클러스터의 kubernetes 서비스는 다음과 같이 같습니다.

$ oc describe svc -n default kubernetes
Name: kubernetes
Namespace: default
Labels: component=apiserver
provider=kubernetes
Selector: <none>
Type: ClusterIP
IP: 172.30.0.1
Port: https 443/TCP
Endpoints: 192.168.181.11:8443
Port: dns 53/UDP
Endpoints: 192.168.181.11:8053
Port: dns-tcp 53/TCP
Endpoints: 192.168.181.11:8053
Session Affinity: ClientIP

참고)
OpenShift DNS
DNS Pods and Services

외부 IP 주소 얻기

  1. Pod 내 애플리케이션이 “master1.example.com” 이름의 사설 DNS IP 주소를 시스템 콜을 이용해 요청한다고 가정합니다.
  2. 애플리케이션의 DNS 질의 도메인이 Pod 의 resolv.conf 의 search 에 나열된 example.com 이 포함되므로 Pod 는 Node 서버(192.168.181.21)의 dnsmasq 에게 즉시 DNS 이름을 질의합니다.
  3. Node 서버(192.168.181.21)의 dnsmasq는 요청 질의의 결과가 캐시나 설정(/etc/hosts)에 없는 경우 DNS 질의를 Node 서버(192.168.181.21)의 origin-upstream-dns.conf 에 지정된 Upstream DNS 서버(192.168.181.5)에 다시 질의합니다.
  4. Upstream DNS 서버(192.168.181.5)(ns 서버)의 dnsmasq 는 자신의 DNS 캐시나 설정(/etc/hosts)을 참조해 “master1.example.com” 의 IP 주소 192.168.181.11 을 Pod(192.168.181.21) dnsmasq 에게 반환합니다.
  5. Pod(192.168.181.21) dnsmasq 는 Pod 에게 “master1.example.com” 의 응답 IP 주소 192.168.181.11를 반환합니다.
  6. Pod 는 애플리케이션에게 IP 주소 192.168.181.11 를 반환합니다.
  7. 찾을 수 없는 경우 Upstream DNS 서버(192.168.181.5)의 resolv.conf 등 나열된 Public DNS(8.8.8.8) 서버 등에 다시 질의합니다.

2017년 8월 31일 목요일

OpenShift TLS SNI Route 메커니즘 분석


  이 문서는 외부 AMQ 클라이언트가 OpenWire over SSL 프로토콜로 OpenShift Router의 SSL/TLS 포트 443을 경유해 OpenShift 클러스트 내 AMQ 파드의 OpenWire over SSL 프로토콜 서비스 포트 61617에 접속할 때, 접속이 가능하게 되는 OpenShift Router 파드의 설정을 분석한 내용입니다. 

2017년 7월 7일 금요일

Apache Kafka 에서 정확히 한 번(Exactly-once)이 가능한가?


  이 문서는 “Exactly-once Support in Apache Kafka” 블로그를 번역한 것입니다. 저자의 블로그에서는 정확히 한번(Exactly-once)의 개념을 설명하고 Kafka로 어떻게 이 개념을 이용할 수 있는지 설명합니다. 그러나 이 개념이 왜 어려운지 또 정말로 가능한지에 대해서는 좀더 성찰이 필요할 것 같습니다.

  우리는 목요일에 의미론적 보증을 극적으로 강화하는 Apache Kafka의 새 버전을 출시 했습니다.

  우리는 빠르고 실용적이며 정확한 방식으로 안정적인 스트림 처리를 수행하는 방법을 수년 간의 고민한 끝에 이 릴리스를 발표했습니다. 구현 노력 자체는 약 1 년 정도 걸렸습니다. 이 과정에서 Kafka 커뮤니티는 약  100 페이지의 세부 설계 문서가 논의했고, 비판도 받고, 광범위한 성능 테스트를 수행했고, 특히 정확이 한번(Exactly-once) 기능을 타깃으로 하는 살인적인 수천 라인의 분산 테스트를 수행했습니다.
 
  이 릴리스에 대한 반응은 대부분 "와우, 정말 멋지네"라는 것이 었습니다. 그러나 나는 불행히도 고전적인 실수를 했습니다. 나는 주석을 읽었습니다. 이것은 우리가 거짓말쟁이라고 주장하는 매우 혼란스러운 사람들의 흥분 섞인 주장들이었습니다.

다음은 반응들 중 일부입니다 .

  "정확히 한 번 전달(exactly-once delivery)는 될 수는 없습니다... 아주 간단한 수학적 정리로 인해 불가능합니다. 또한 저자가 혼란스러워서 독자가 모든 것을 불신하게 만드는 것은 의심의 여지가 있습니다. "

  "수학적으로 입증된 사실에 어떻게 대처할 지 재미있는 기사. 이 기사에서 주의 깊게 지정하지 않은 가정을 변경하지 않는다면 모든 경우에 작동할 수는 없으며 이는 시스템에 관한 것입니다. "

  나는 이 반응들이 틀렸다는 것을 믿습니다. 당신이 이것을 생각하는 사람들 중 하나라면, 저는 우리가 실제로 불가능하고 불가능한 것이 무엇인지, 그리고 Kafka에 무엇이 세워 졌는지를 실제로 들여다 보라고 요청합니다. 그리고 더 많은 정보에 입각한 의견을 얻으시길 바랍니다.

  이 부분을 두 부분으로 나누어 봅시다. 첫째, 정확히 한 번(exactly-once)은 이론적으로 불가능한가? 둘째, 어떻게 Kafka는 이 기능을 지원하는가?

정확히 한 번은 불가능한가요?


  정확히 한 번 전달/의미론이 수학적으로 불가능하다는 것의 정확한 이유를 모른 체 사람들의 확신해 찬 주장이 주변을 떠돌아 다니고 있습니다. 그러나 이것이 분명히 일반적인 지식임에도 불구하고 사람들은 이것이 어떤 증거인지 또는 정확히 한 번 의미가 무엇인지에 대한 정확한 정의에 연결되는 경우는 드뭅니다. 이것들은 FLP 결과 나 Two Generals Problem 와 같은 것들을 증거로 연결하지만 정확히 한 번(exactly-once)에 관한 것은 아닙니다. 분산 시스템에서 가능한 것(비동기, 반동기 등)을 제어하는 설정과 또 그것이 무엇인지를 정확하게 기술하지 않는다면 가능하거나 불가능한 것을 이야기 할 수 없습니다. 그리고 나쁜 일이 일어날 수 있는 것을 설명하는 결함 모델도 이야기 할 수 없습니다.

  그러면 우리가 성취하고자 하는 것과 같은 속성을 공식적으로 정의할 수 있는 방법은 있는 걸까요?

  네 있습니다. 그런 속성이 있다는 것이 밝혀졌습니다. 그것은 "원자적 브로드캐스트(Atomic Broadcast)" 또는 "총 주문 브로드캐스트(Total Order Broadcast)" 라고 합니다. 다음은 널리 사용되는 분산 시스템 교과서들 중 하나의 정의입니다.


  읽어보십시오. 내 생각에 이것은 pub/sub 메시징의 컨텍스트에서 사람들이 정확히 한 번 전달한다는 의미입니다. 즉, 메시지를 게시할 수 있으며 하나 이상의 수신 애플리케이션에 정확히 한 번 전달됩니다.

  그렇다면 원자적 브로드캐스트를 해결할 수 있을까요?

  해결할 수 있습니다. 제가 사진을 찍은 책 외에도 수십 개의 알고리즘을 비교 분류한 논문을 읽을 수 있습니다 .

  그러나 분산 시스템 책을 읽지 못하면 어떻게 이것이 사실이라는 것을 스스로에게 확신시킬 수 있습니까?

  원자적 브로드캐스트는 합의(consensus)와 동등한 것으로 드러났으므로 우리는 합의가 이루어 질 수 있는지 여부를 이해하는 것으로 문제를 좁힐 수 있습니다. 이는 아마도 합의가 분산 시스템에서 가장 많이 연구된 문제이기 때문입니다.

  합의가 가능한 것일까요? 아마 그렇다고 느낄 수 있습니다. 이는 Paxos 및 Raft 와 같은 잘 알려진 알고리즘에 의해 공격받는 문제이고 현대 분산 시스템 실행에 널리 의존하기 때문에 발생합니다. 그러나 이론적인 결과를 원한다면 설정 및 실패 모드에 대해 구체적으로 설명해야합니다. 예를 들어 의견에 있는 몇몇 사람들은 "하나의 잘못된 프로세스를 가진 합의의 불가능성"이라는 제목의 "FLP"논문을 인용했습니다. 그것은 좋아 보이지 않습니다! 첫 번째 문장에서 실패 감지기가 "충돌 오류가 있는 비동기 시스템에서 합의를 해결하는 데 사용될 수 있다"고 주장하는 논문을 쉽게 찾을 수 있습니다. 이것을 어떻게 이용할까요? 이것은 이론적으로 분산된 시스템 주장에서 세부 사항이 중요한 부분입니다.  우리는 설정과 결함 모델에 대해 구체적이어야 합니다. FLP 결과는 매우 제한적인 환경에서 합의가 가능하지 않다는 것을 증명합니다. 로컬 타이머 또는 무작위화와 같은 간단한 작업을 허용하면 가능해집니다. 합의 알고리즘은 이런 것들에 의존하여 일종의 시끄럽지만 결국에는 "올바른 시간 동안 하트 비트가 없는 프로세스가 죽었다"와 같은 올바른 오류 감지를 구현합니다. 이것들이 사람들이 "합의를 이끌어 낸다" 알고리즘을 말할 때 사람들이 참조하는 설정입니다.

  (FLP 외에도 많은 사람들이 Two Generals 문제를 "수학적 정리"와 연결시켰습니다. 그 이유는 실제로 전통적인 메시징 시스템의 유추를 볼 수는 있지만 실제로 Kafka는 그 문제와 별로 비슷하지 않습니다.)

  이것은 깊은 주제입니다. 관심이 있으시면 첫 걸음으로 Martin Kleppmann의 멋진 책을 추천 할 수 있습니다 . 진정으로 사로 잡힌 사람들은 참고 문헌들로 한 달 동안은 바쁘게 지낼 수 있을 것입니다.

  그렇다면 어떻게 이것을 실제로 실천할 수 있을까요? 실용적인 측면에서, 합의는 현대 분산 시스템 개발의 주류입니다. AWS에서 거의 모든 서비스를 사용하거나 AWS를 기반으로하는 서비스 위에 구축된 서비스를 사용했다면 합의로 구축된 시스템에 의존하게 됩니다. 이것은 현재 구축되는 시스템이 많지는 않지만 많은 경우에 해당됩니다. Kafka 는 이 중 하나이며, 그 중심적 추상은 분산된 일관된 로그이며, 사실상 가장 순수한 아날로그에서 다중 라운드로의 합의입니다. 따라서 합의가 가능하다고 믿지 않는다면 Kafka도 가능하다고 믿지 않습니다. 이 경우 Kafka에서 정확히 한 번 지원될 가능성에 대해 너무 걱정할 필요가 없습니다!

Kafka로 정확히 한 번 전달되는 애플리케이션을 어떻게 만들 수 있습니까?


  Kafka는 다음과 같은 로그를 갖습니다.

Apache Kafka의 로그

  Kafka의 로그는 강하게 정렬된 레코드 순서며 각 레코드에는 로그의 레코드 위치를 식별하는 순차적 숫자 오프셋이 지정됩니다.

  "생산자"는 이 로그에 레코드를 추가하고, 0 이상의 소비자 애플리케이션은 자신이 제어하는 ​​지정된 오프셋에서 메시지를 읽습니다.

  다음과 같은 애플리케이션을 상상해 봅시다.

  게시자가 메시지를 게시하려고 하고 소비자가 메시지를 읽고 이를 데이터베이스에서 저장하려고 합니다. 우리는 어떻게 이것을 할 수 있고 올바른 해결책을 얻을 수 있을까요?

  발생할 수 있는 두 가지 범주의 문제를 볼 수 있습니다.
  1. 첫 번째 문제는 게시자 애플리케이션이 로그에 메시지를 기록하지만 네트워크를 통해 확인 응답을 받지 못하면 발생합니다. 이것은 이 게시자를 묶어 놓을 것입니다. 이 때 메시지는 실제로 쓰기가 성공했거나 Kafka에 전혀 도착하지 않았을 수 있습니다. 우리는 모릅니다! 우리가 재시도하고 메시지 쓰기가 성공했다면 우리는 복제본을 가질 수 있습니다. 우리가 재시도하지 않고 쓰기가 성공하지 못했다면 우리는 메시지를 잃을 것입니다. 이는 기본 키가 없거나 자동 증가 기본 키가 없는 데이터베이스 테이블에 삽입했을 때와 실질적으로 동일한 딜레마입니다.
  2. 두 번째 문제는 소비자 측면에 있습니다. 소비자 애플리케이션은 로그에서 일부 메시지를 읽고 데이터베이스에 결과를 쓸 수 있지만 위치를 표시하는 오프셋을 업데이트하기 전에 실패할 수 있습니다. 해당 소비자가 다시 시작될 때 (잠재적으로 Kafka 그룹 관리 메커니즘을 사용하는 다른 컴퓨터에서 자동으로) 중복될 수 있습니다. 애플리케이션이 저장된 오프셋을 먼저 업데이트 한 다음 데이터베이스를 업데이트하는 경우, 실패로 인해 재시작시 업데이트가 누락 될 수 있습니다.
  두 문제에 대해 이야기 해 봅시다. 첫 번째 문제는 우리가 게시물에서 발표한 멱등성 지원에 의해 해결됩니다. 따라서 게시자는 중복 가능성에 대한 걱정 없이 성공할 때까지 항상 다시 시도할 수 있습니다 (Kafka는 투명하게 탐지하여 무시합니다).

 믿거나 말거나, 우리는 두 번째 문제에 대해서는 깊이 생각하지 않았습니다. 그러나 우리가 그것을 생각하지 않았기 때문이 아닙니다! Kafka를 알고 있는 사람들을 위해 이미 긴 블로그 게시물이 있기 때문에 우리는 더 깊이 뛰어 들지 않았습니다. 그래서 짧은 요약 설명을 했습니다.

  다음은 좀 더 깊이 있는 토론입니다.

  소비자가 정확히 한 번 처리하도록 하려면 생성된 파생 상태와 업스트림을 가리키는 오프셋을 동기화 상태로 유지해야 합니다. 여기서 중요한 사실은 소비자가 로그에서 오프셋을 제어할 수 있고 원하는 위치에 저장할 수 있다는 것입니다. Kafka 위에 정확하게 한 번 의미를 얻기 위해 이 방법을 사용하는 일반적인 두 가지 방법이 있습니다.
  1. 파생된 상태와 오프셋을 동일한 DB에 저장하고 트랜잭션에서 둘 다 업데이트하십시오. 다시 시작할 때 DB에서 현재 오프셋을 읽고 거기에서 읽기 시작하십시오.
  2. 모든 상태 업데이트와 오프셋을 멱등성(idempotent) 방식으로 작성하십시오. 예를 들어 파생 상태가 발생 횟수를 추적하는 키와 카운터인 경우 오프셋과 함께 카운터를 저장하고 오프셋 <= 현재 저장된 값으로 모든 업데이트를 무시합니다.
  좋습니다. 하지만 "어렵습니다!"라고 반대할 수도 있습니다. 실제로 그렇게 힘들지는 않다고 생각합니다. 그럼에도 저도 트랜잭션은 간단하지 않다고 생각합니다. 여러 테이블을 업데이트하는 경우 트랜잭션 문제가 발생합니다. 오프셋 테이블을 추가하고 이를 업데이트에 포함시키는 것은 로켓 과학이 아닙니다.

  이에 대해 제가 들었던 또 다른 반대는 실제로 "정확히 한 번"이 아니라 실제로는 "효과적 한 번(effectively once)"이라는 것입니다. 나는 (일반적으로는 덜 이해되지만) 이런 측면이 더 좋다고 동의합니다만, 우리는 여전히 정의되지 않은 용어의 정의에 대해 논쟁 중입니다. 우리가 전달과 관련해 잘 정의된 속성을 원한다면 나는 실제로 원자적 브로드캐스트(Atomic Broadcast)가 꽤 좋은 정의라고 생각합니다. 우리가 비공식적으로 말하면, 사람들은 의미에 대해 직관적인 생각을 갖기 때문에 "정확히 한 번"이라고 말하는 것이 좋습니다. (우리가 원자성 에 대한 지원을 발표했다더라도 혼란은 결코 더 적지 않았을 것입니다. ). 더 큰 비판은 사람들이 원하는 진정한 보증이 "정확히"도 "효과적"도 아니며 "한번" 또는 "전달"과 관련된 어떤 것이라는 것입니다. 사람들이 원하는 진정한 보증은 애플리케이션과의 통합에 대해 열심히 생각할 필요없이 오류가 발생했을 때 메시지를 철저히 정확하게 처리하는 것입니다.

  결국, 제가 설명한 해결책은 그다지 복잡하지는 않지만 여전히 애플리케이션의 의미에 대해 생각해야 합니다. 우리는 "애플리케이션에 마법의 요정 가루를 내 뿌릴 수 있습니까" 라는 제목의 블로그에서 이 문제를 다루려고 했습니다 (대답은 "아니오"였습니다).

  우리는 이것을 쉽게 할 수 있을까요? 우리는 할 수 있다고 생각합니다. 여기에서 기능 집합의 두 번째 부분인 트랜잭션이 등장합니다.

  실제로 위에서 제시한 예는 데이터 처리와 결과를 저장 시스템에 통합하는 다른 두 문제를 혼합한 것입니다. 이들은 서로 얽혀 있기 때문에, 개발자는 두 가지 방법을 함께 풀어 내기가 어렵습니다.

  이를 개선하기 위한 아이디어는 애플리케이션을 다른 두 부분으로 분류하는 것입니다. 이 두 부분은 하나 이상의 입력 스트림을 변환하는 (레코드 간에 결합되거나 측면 데이터와 결합될 수 있는) "스트림 처리" 부분과 이 데이터를 데이터 저장소로 전송하는 (동일한 애플리케이션 또는 프로세스에서 실행될 수 있지만 논리적으로는 구분된) 커넥터입니다.

  커넥터는 Kafka로부터 특정 데이터 시스템에 대한 트랜잭션 또는 멱등적 데이터 전달에 대한 추론을 필요로 합니다. 커넥터는 깊게 생각하고 오프셋을 관리해야 하지만 완전히 재사용할 수 있습니다. JDBC를 지원하는 모든 데이터베이스에서 JDBC 커넥터를 정확히 한 번은 제대로 작동하므로,  애플리케이션 개발자는 이를 고려할 필요가 없습니다. 우리는 이미 이런 것들을 가지고 있습니다, 그러므로 이 기능은 개발하기 보다 JDBC 커넥터를 다운로드합니다.

  어려운 부분은 데이터 스트림에서 범용 변환을 올바로 수행하는 것입니다. Kafka의 스트림 API와 함께 트랜잭션 지원도 필요합니다.

  Kafka 스트림 API 는 입력 스트림과 출력 스트림의 상단에 변환을 정의하는 매우 일반적인 API를 제공하는 생산자 및 소비자의 상위 계층입니다. 이 API를 사용하면 애플리케이션에서 수행 할 수 있는 거의 모든 작업을 수행 할 수 있습니다. 고전적인  메시징 시스템 API에 비해 더 강력하지는 않습니다.

  Kafka 스트림 애플리케이션:

  이 애플리케이션은 분산된 "단어 수"를 계산하는 하는 고전적인 빅데이터 예제입니다. 단어 수는 완전히 실시간이며 연속적입니다 (새 문서가 작성 될 때마다 카운트가 증가합니다).

 이 프로그램은 메인 메소드를 가진 보통의 자바 애플리케이션입니다. 이 애플리케이션은 보통의  애플리케이션들처럼 시작되고 배포됩니다. Kafka 소비자는 모든 인스턴스가 들어오는 데이터 스트림을 처리하도록 작동합니다.

  이 애플리케이션은 어떻게 정확성을 보장할 수 있습을까요? 결국엔 입력, 출력, 수신 메시지 전반의 집계 및 분산 처리 등 모든 복잡한 것들을 상상할 수 있습니다.

  그것에 대해 생각해보면 Kafka의 모든 스트림 처리는 다음 세 가지를 수행하고 있습니다.
  1. 입력 메시지 읽기
  2. 상태에 대한 업데이트를 생성(애플리케이션 인스턴스가 실패하고 다른 곳에서 복구되는 경우 내결함성이 필요하기 때문에)
  3. 출력 메시지 생성
  핵심 요구 사항은 이 세 가지가 항상 함께 발생하거나 전혀 발생하지 않도록 보장하는 것입니다. 상태가 업데이트되었지만 출력이 생성되지 않거나 그 반대로 오류가 발생하는 경우를 허용할 수 없습니다.

  우리는 어떻게 이것을 할 수 있을까요?

  우리는 수년에 걸쳐 이것에 관해 정말로 열심히 생각했고, 지금도 이것을 건설하고 있습니다. 기초 작업은 지난 몇 년 동안 한 번도 변경하지 않은 사항이었습니다.
  1. 0.8.1 릴리스에서 Kafka는 상태 변경을 위한 저널 및 스냅샷으로 사용할 수 있는 로그 압축을 추가했습니다. 즉, 임의의 로컬(디스크 또는 메모리 내) 데이터 구조에 대한 일련의 업데이트를 Kafka에 대한 일련의 기록으로 모델링할 수 있습니다. 이를 통해 로컬 연산의 내결함성을 만들 수 있었습니다.
  2. Kafka에서 데이터를 읽는 것은 오프셋을 증가시킵니다. 0.8.2 에서  오프셋 저장에 Kafka 자체를 사용하도록 오프셋 저장 메커니즘을 이동했습니다. 내부에서 오프셋 커밋(Commit)은 Kafka에 쓰여집니다. (소비자 클라이언트가 이 작업을 수행하므로 우리는 모를 수도 있습니다).
  3. Kafka로 데이터를 쓰는 것은 항상 Kafka에 쓰기였습니다.
  위 프로그램은 방금 추가한 (이 세 가지 작업을 투명하게 단일 트랜잭션으로 감싸는) 기능을 설정합니다.   이렇게 하면 읽기, 처리, 상태 업데이트 및 출력 모두가 함께 발생하거나 전혀 발생하지 않습니다.

  이 과정은 느리지 않을까요? 많은 사람들은 분산 트랜잭션이 본질적으로 매우 느리다고 가정합니다. 모든 단일 입력에 대해 트랜잭션을 수행할 필요는 없습니다. 이 경우 입력들을 함께 배치로 처리할 수 있습니다. 배치가 클수록 트랜잭션의 실제 오버헤드는 낮아집니다 (트랜잭션은 트랜잭션의 메시지 수와 관계 없이 일정한 비용을 가집니다). 블로그 포스트는 이것에 대한 성과 결과를 보여 주었고 이는 매우 유망한 성과였습니다.

  결과적으로 스트림 API를 사용하는 애플리케이션에 내 애플리케이션을 인수로 지정하고 출력 시스템과의 통합을 위해 정확히 한 번 커넥터를 사용하는 경우, 이제는 구성 변경만으로 종단 간 정확성을 얻을 수 있습니다.

  정말 멋진 점은 이 기능이 Java API에 전혀 묶여 있지 않다는 것입니다. Java API는 데이터 스트림의 연속적이고 상태를 유지하고 올바른 처리를 모델링하기 위한 범용 네트워크 프로토콜을 둘러싼 단순한 래퍼입니다. 모든 언어에서 이 프로토콜을 사용할 수 있습니다. 우리는 이 기능에  일종의 매우 강력한 클로저 속성을 추가함으로 변환을 수행하고 프로토콜을 구현하는 임의의 프로세스를 통해 입력 및 출력 토픽을 올바르게 연결한다고 생각합니다

정확히 한 번에 대해 생각해 보기로 돌아가기


  나는 회의적인 사람들에게 우리가 한 일을 이해하고 그것을 이해하도록 격려하기 위해 이 글을 썼습니다. 벤더로부터 오는 많은 헛소리들에 나는 회의적입니다. 그러나 나는 이 기능 세트가 정말 흥미롭고 LIAR을 모든 대문자로 외칠뿐 아니라 실제로 무엇을하는지,하지 않는지, 실제로 한계가 무엇인지 이해함으로써 이해가 훨씬 더 높아질 것이라고 생각합니다.

  업계에서는 올바른 결과를 얻을 수 없다는 것, 근본적으로 비효율적이고, 일괄처리 없이는 불완전하다는 것 등,  롤백되는 과정에 있는 스트림 처리와 관련하여 많은 가정이 있었습니다. 나는 정확히 한 번 처리하는 것이 불가능하다는 주변의 광범위하고 모호한 주장이 결국 양동이에 빠지게 된다고 생각합니다. 그것들은 나에게 일종의 분산 시스템인 broscience("나는 형제의 형제에게서 구글에서 일하는 형제가 정확히 한번이 CAP 이론에 위배된다는 말하는 것을 들었습니다”)를 생각나게 합니다. 나에게 진보는 일반적으로 실제로 가능하지 않은 것을 더 깊이 이해하고 문제를 재정의하여 우리를 앞으로 나아갈 실제적인 추상화를 구축하려는 시도로 이루어집니다.

  이런 것이 일어나는 좋은 예는 Spanner 와 CockroachDB 와 같은 시스템에서 수행되는 작업입니다. 이 작업은  가능한 범위 내에서 애플리케이션 개발자에게 유용한 기능을 제공하기 위해 많은 것을 수행합니다. 나는 NoSQL 분야에서 많은 경험을 갖고 있습니다. 이 시스템이 무엇을 하고 있는지는 대부분의 사람들이 불가능하고 비현실적인 조합이라고 생각하는 것으로 잘못 인식되었습니다. 나는 이것이 우리에게 교훈이되어야 한다고 생각한다. 애플리케이션을 구현하는 가난한 사람에게 모든 어려운 문제를 포기하고 구멍을 내기보다는 문제 공간을 재정의하여 올바른, 빠르며 가능한 대부분의 사용 가능한 시스템 기본 요소를 구축하는 방법을 이해해야 합니다.

원문 : Exactly-once Support in Apache Kafka