stream kafka app 에 rabbitmq sleuth 사용하기

Cloud 2018.01.31 20:06 posted by dev.bistro

현재 팀은 SpringBoot application 을  좀 더 운영하기 쉽도록 하기 위해서 starter를 하나 생성해서 사용하고 있다.

cloud-netflix 셋팅과, 그 많고 많은 zuul, eureka 버그 픽스를 위한 몇몇 코드, 그리고 config server를 기반으로 한 다양한 설정들을 공통화 하기 위한 목적이다. 현재 진행 하는 프로젝트는 이 starter 를 쓰지 않고 순수하게 spring/kafka 만을 사용 하고 있다.

이 프로젝트를 위해 별도의 metrics 시스템을 구축하기 보다는 기존의 sleuth-zipkin 인프라를 활용하기 위해서 spring-cloud-sleuth-stream을 사용하려 한다.


1. gradle 설정 추가

['stream-kafka'].collect {
compile "org.springframework.cloud:spring-cloud-starter-$it"
}
compile 'org.springframework.cloud:spring-cloud-stream'

에서 

compile 'org.springframework.cloud:spring-cloud-sleuth-stream'
compile 'org.springframework.cloud:spring-cloud-stream-rabbit'
compile "org.springframework.cloud:spring-cloud-starter-sleuth'

를 추가하였다.


2. 설정 추가

spring:
application:
name: pacman
rabbitmq:
addresses: 172.18.176.196:5672
username: rabbitmq
password: rabbitmq
sleuth:
sampler:
percentage: 1.0
stream:
enabled: true
zipkin:
service:
name: vine-event-pacman

* zipkin.service.name 을 추가해야만 했다. application.name 을 그대로 사용 할 줄 알았는데.. 어디선가 자동으로 해주고 있는 거였나보다.

이렇게만 설정하면 아래와 같은 에러를 볼 수 있다.  

현재 우리의 zipkin은 rabbitmq 기반인데, 하필이면 이 프로젝트는 binder로 kafka를 사용하고 있었다.
당연하게 stream binder로 kafka, rabbit를 동시에 쓰고 있어서 발생한 문제였고, DefaultBinderFactory 를 확인하여 default binder 를 설정함으로서 해결 할 수 있었다.

Caused by: java.lang.IllegalStateException: A default binder has been requested, but there is more than one binder available for 'org.springframework.integration.channel.DirectChannel' : kafka,rabbit, and no default binder has been set

cloud:
stream:
default-binder: "rabbit"



kafka stream의 fail-over & high-availability

Cloud 2018.01.30 19:55 posted by dev.bistro

18/01/30 idea


카프카 자체의 data recovery는 훌륭하다. 당연한게 파일로 저장하고 심지어 여러 셋트로 저장한다. 유실되는 경우는 저장 기간을 넘기거나, 1개의 IDC에만 설치한 후 IDC가 물에 잠기는 방법 뿐이다. 이건 다 아는 얘기고, 문제는 이 kafka 를 기반으로 한 streams 를 서비스 할 때이다.


1. Kafka는 기본적으로 1 개의 TopicPartion 에서 1 개의 Consumer Group의 노드가 접속 한다.

2. Kafka Topic은 KStream/KTsble/StateStore 등을 통해서 각각의 topic-partition을  local DB에 싱크를 맞춘후 materilized 한다  (비동기/latency 존재)


우선 1번의 문제부터....

1. 해당 TopicPartion의 정보를 단 1개의 VM(instance)만이 가지고 있을 수 있다 (Application Cluster 자체를 active-standby로 구성하지 않는이상)

 이 얘기는 해당 Node가 죽어버리면 '그 순간' 그 데이터들은 VM instnace안에서 존재 하지 않는 다는 것이다. 

 물론 Kafka Broker 내에는 존재하기 때문에 일정 시간의 기다린다면 새로운 Instacne가 해당 데이터를 살릴 수 있다. 하지만 그 '일정 시간의 텀'이 문제이다.

 위에서 얘기한 데로 Application 자체를 standby로 준비하지 않는 이상 힘들다. kafka 역시 standby 로 해결한다.

 링크 : https://kafka.apache.org/10/documentation/streams/developer-guide/config-streams.html#id10

 동일한 셋을 하나 더 구성하지만 그 녀석은 말그대로 standby이다. 실제 서비스에는 투입이 되지 않으며, resource 역시 많이 차지하지만, 장애 시간을 짧게 가져가는데 필수적인 요소이다.

 StreamThread 내에서 task를 active 와 standby로 가지고 있다. 


 이 부분은 ... 계속 파야할 것 같다.



2. Event가 topic으로 전송이 된다면 해당 topic-partition을 materialized view 로 가지고 있는 consuer group 의 instance는 consumer로 붙는다.

   이 때 topic과 state-store의 데이터 불일치는 필연적이다.

   이 때 state-store에서 get을 하는 '재고차감' 같은 Domain은 어떤 식으로 처리 할 것인가?

   A. 모든것을 sync로 한다 - 의미 없으니 pass

   B. instance의 state-store를 ReadyOnlyQueryType으로 사용하는게 아닌 WriteableQueryType으로 사용하여, put도 할 수 있게 한다. 그리고 이 changelog는 

      remote kafka cluster와 연동한다. 

      atmoic을 보장 할 수 있는 방법이고, 서버가 죽었을때도 문제가 되지 않는다. 내부 rocksdb에 데이터는 유지되므로 다시 살리기만 한다면 cluster와 동기화를 할 수 있을테니까..

      문제는 server가 살릴수 없을 때이다. 이 이슈는 바로 데이터 유실로 이어진다.



적당한 방법들은 보이지만, 정답인지 아닌지 확신이 안선다.

      



Spring Cloud Stream 의 버전 선택

Cloud 2018.01.28 21:53 posted by dev.bistro

밑에는 잡담이니 결론 부터

* 결론 Ditmars.SR3 사용,  Spring 4, Boot 1.5.x는 기존대로 유지
* Kafka 는 0.1x 유지
* spring-cloud-stream-reactive & reactor 3.1.X 사용 ( rxjava2 버림 )




* 우리가 사용중인 Spring Cloud Dalston SR5는 Ditmars 버전 매트릭스에 맞지 않다 이전 버전인 Chelsea.SR2를 고려해야한다.

* 하지만 Ditmars.SR3 버전으로 리서치를 진행 할 예정이다. 

* Ditmars.SR3 은 spring-kafka 1.1.7 이므로 @EnableStream 사용등은 커스텀마이징을 해야 하기 때문에 추천버전은 Kafka 0.10을 그대로 사용 해서 진행 한다


Ditmars.SR3 의존성 정보

Spring Cloud Stream Core 1.3.2.RELEASE
Spring Cloud Stream Binder Kafka 1.3.2.RELEASE
Spring Boot : Spring Boot 1.5.7 or a later 1.5.x release. It is not compatible with Spring Boot 2.x.
Spring Cloud : Spring Cloud Edgware.SR1


라이브 러리 버전 상세 정보

- 링크 : https://github.com/spring-cloud/spring-cloud-stream-starters/blob/vDitmars.SR3/spring-cloud-stream-dependencies/pom.xml

- 링크 :  https://github.com/spring-cloud/spring-cloud-stream/blob/v1.3.2.RELEASE/pom.xml

- reactor : 2.0.8.RELEASE (default)

- rxjava : 1.1.10 (optional 이므로 spring-cloud-stream-rxjava 를 추가)



spring-cloud-stream-reactive


* reference : https://docs.spring.io/spring-cloud-stream/docs/Ditmars.SR3/reference/htmlsingle/#_reactive_programming_support

* spring-cloud-stream 에 reactive API를 사용하기 위한 지원용 라이브러리이다.

* 기본은 reactor 이며, ConditionalOnClass 에 의해 rxjava 를 지원하지만 가장 최신 버전까지도 rxjava 1.x 만을 지원한다.

* 추가로 @StreamEmitter 어노테이션이 추가된다. 

* ReactiveSupportAutoConfiguration 가 존재하며, rx.Observable 클래스를 확인 한후 rxjava 를 지원한다

* 아래의 Adapter 들을 Spring bean으로 등록시켜서 Observable type을 처리해준다.

  - MessageChannelToInputObservableParameterAdapter

  - MessageChannelToObservableSenderParameterAdapter

  - ObservableToMessageChannelResultAdapter

* rxjava를 굳이 쓰겠지만 rxjava1을 쓰는게 속편하다, 하지만 rxjava2 를 현재 쓰고 있는데 down-grade할 정도로 필요성은 있어 보이지 않고

그냥 이번기회에 (어차피 옮길거) reactor를 써보기로 함

Kafka 기반의 event driven stateful microservices

Cloud 2018.01.28 12:58 posted by dev.bistro


18/01/26 idea


구현해야 할 도메인은 https://www.confluent.io/blog/building-a-microservices-ecosystem-with-kafka-streams-and-ksql/

필요한 내용은

- 정확한 request/ responses
- scale out
- high availability
- ktable & state-store를 기반으로 한 local - storage의 사용

블로그 글과 예제에 대해서는 100퍼센트 공감이 되지만, 몇몇 부분에서는 '구현' 자체가 고민스러움.
나 뿐 아니라 다른 이의 구현을 살펴보아도 동일하다.  아래의 github 개발자도 위의 블로깅 내용을 보고 example project를 작성하였지만 같은 문제점을 그대로 노출하고 있다..  

- https://github.com/hpgrahsl/kafka-streams-emojitracker
- 사용 : confluent connect, stream, statestore, reactive(boot2.0)
- 특징 : 트랜잭션 사용안함(at_least_once) / 

- 데이터 서칭

더보기

이 분은 그리고 request/reply 모델이 아니다.  그래서 아래와 같은 부분에 대해서는 구현이 빠져있다.
https://github.com/confluentinc/kafka-streams-examples/blob/4.0.0-post/src/main/java/io/confluent/examples/streams/microservices/OrdersService.java#L232 

reply에 대한 고민은 
- 채번을 하고, 해당 Order Id의 reply를 consumer로 받을 수 있는 Node으로 요청을 보낸다.
  이슈) zuul, client load loadbalancer가 주키퍼를 기반으로 브로커 파티션/파티셔너 정보를 알고 있어야 한다.
- zuul 에서 기존 routing 정책에 의해 요청을 보낸후, 실제 Node에서 '응답을 받을 수 있는' 채번을 한다.
  이슈) Range(0,PartionNum).boxed().filter(local-state::matchingkey).findFirst();
     그럼 아마 ID가 1,7,11 이런식으로 생기겠지. (중간에 사용못하는 애들 생김)

- 또는... 정말 옛날로 돌아간다. (생각도 안해봤지만, 팀원분께서 차라리 이럴꺼면 2번 호출해라고)
  ex) 채번 자체는 Node 에서 하고, 이벤트를 발생한다. 그리고 그 노드가 '응답'을 받는 다는 보장이 없으므로 Mono로 FT로 바로 리턴, FT는 응답을 바로 받고 'OrderId'를 가지고 다시 한번 zuul 영역을 호출한다. -_-;

- 또는 제일 심플하게 각각의 Node를 Consumer Group으로 사용하지 않는다!
  이슈) 트래픽이 WAS 만큼 '배수'로 든다. 즉 기존에 WAS 50대로 10MB/SEC 트래픽이었다면 이렇게 하면 500MB/SEC -_-;;;

AWS EC2에 tomcat7 & java8 설치

Cloud 2014.07.09 22:43 posted by dev.bistro

기본으로 톰캣6, 자바7이 깔려있었다 (micro instance 기준)
참고 : http://blog.de-gouveia.eu/2014/05/21/java-8-jdk-linux-installation-in-ec2-linux-instance/


* 첫번째로 java8 설치.
yum에는 아직 java-1.8이 없어서 직접 오라클에서 다운받아야한다.
파일로 받아서 scp로 올리면 귀찮으니 위 블로그처럼 다운로드 받음 


wget -c --no-cookies --no-check-certificate --header "Cookie: gpw_e24=http%3A%2F%2Fwww.oracle.com%2F; oraclelicense=accept-securebackup-cookie" "http://download.oracle.com/otn-pub/java/jdk/8u5-b13/jdk-8u5-linux-x64.rpm" --output-document="jdk-8u5-linux-x64.rpm"


sudo rpm -i jdk-8u5-linux-x64.rpm


lrwxrwxrwx 1 root root 22  6월 11 16:52 java -> /etc/alternatives/java
lrwxrwxrwx 1 root root 27  7월  9 02:05 javac -> /usr/java/default/bin/javac
lrwxrwxrwx 1 root root 29  7월  9 02:05 javadoc -> /usr/java/default/bin/javadoc
lrwxrwxrwx 1 root root 28  7월  9 02:05 javaws -> /usr/java/default/bin/javaws


lrwxrwxrwx 1 root root 16 7월 9 02:05 default -> /usr/java/latest
lrwxrwxrwx  1 root root   21  7월  9 02:05 latest -> /usr/java/jdk1.8.0_05

설치를 완료후 확인하면 위와 같이 되어 있고,   /usr/java/default는 방금 설치한 1.8.0으로 링크지만
java만은 alternatives/java를 보고 있어서 아래처럼 수정을 했다

lrwxrwxrwx 1 root root 30 7월 9 02:13 java -> /usr/java/jdk1.8.0_05/bin/java



* 두번째로 톰캣7 설치

Servlet3을 위해 톰캣6이 아닌, 7을 써야하는데 yum보단 바로 설치했다

wget http://apache.tt.co.kr/tomcat/tomcat-7/v7.0.54/bin/apache-tomcat-7.0.54.tar.gz
tar zxpvf apache-tomcat-7.0.54.tar.gz

위치는/var/share/tomcat7 로 변경하고  /etc/init.d/에 기존의 tomcat6은 놔둔채 tomcat7 스크립트를 추가로 생성

스크립트 참고 : http://blog.maestropublishing.com/2013/04/23/tomcat7-service-startstop-script/



TAG aws, EC2, install

google cloud compute engine 시작하기.

Cloud 2013.11.22 23:40 posted by dev.bistro

aws의 ec를 습득하고 나서, asgard, ice도 추가로 알면서 aws관리에 좀 더 흥미를 느낀 이 시점에, 구글이 같은 IaaS인  Google Compute Engine을 발표하였다.덤핑하기 시작했다. 2000$의 프리코드와 함께~  뭐 공짜이니 안할수야 없지. 한번 시작해 보도록 한다.


계정 등록과, Compute 2000$ 등록은 이미 되었다고 생각하고 그 이후 진행을 해보자. 진행 방법은 cloud.google.com 이 아닌 gcutil을 이용한 command line모드로 진행한다. 아래 링크를 참고했다.
https://developers.google.com/compute/docs/quickstart


1. gcutil 의 설치 : https://developers.google.com/compute/docs/gcutil
python으로 작성된 gcutil을 다운로드 한다. python3.X은 지원안하니 주의.

2. 그 다음 사용할 프로젝트를 기본으로 셋팅하자.

$ gcutil getproject --project=<project-id> --cache_flag_values

--cache_flag_value를 이용하면, 디폴트 프로젝트 정보를  ~/.gcutil.flags. 에 저장해줘서 매번 설정을 안해줘도 된다 만약 이 옵션을 안하면 매번 --project를 지정해야 하니 불편하겠지?


만약 여기에서 resource를 계속 못찾는다고 하면, ( Error: The resource 'projects/?????' was not found ) ' https://cloud.google.com/console#/project ' 에서 사용 원하는 프로젝트를 선택한 다음 결제 정보를 입력해준 다음 프로젝트를 활성화 시켜야 한다.

 이후 ' gcutil getproject' 를 입력하면 현재 project 정보를 확인 할 수 있다.


3.  기본적으로 GCE(google compute engine)는 모든 네트웍이 막힌 상태로 시작하기 때문에 최소한 웹서비스를 구동하려면 http 80 port는 열어줘야한다.

단 기억하고 있을점은 2개의 default firewall은 등록되어 있다는 것이다.

  • A firewall that allows SSH access to any instance.
  • A firewall that allows all communication between instances in the same network.

> gcutil listfirewalls 으로도 확인 할 수 있다.

 gcutil addfirewall http2 --description="Incoming http allowed." --allowed="tcp:http"

http2라는 이름으로 tcp:http (tcp:80과 동일) 이라는 네트웍을 허용시키라는 뜻이다. (apache web page는 기본적으로  http 80 port임을 기억하면된다)
역시 해당 정보는  > gcutil getfirewall http2로 확인 할 수 있다.


4.  실제 VM instance를 만들 차례이다.

gcutil addinstance ins1 --persistent_boot_disk

처럼 부팅가능한 ins1 인스턴스를 만들도록 하자. 위의 명령어를 입력하면 인스턴스를 생성하기 위한 몇가지 질문을 받는다.


아직은 Zone이  US와 Europe만 존재를 하고, 머신 타입은 22가지가 존재한다. 테스트용이니 제일 저렴한 녀석이 좋겠지? 가격 정책은 https://developers.google.com/compute/pricing#machinetype 여길 참고
OS는 centos 와 debian이 있는데 이까지 선택을 하면 실제적으로 VM instacne 생성 작업이 몇초 동안 진행된다.



이렇게 만들어진 인스턴스들은

gcutil listinstances 
gcutil getinstance ins1

의 명령어도 인스턴스목록, 해당 인스턴스의 기본정보를 확인 할 수 있다. 


기본적으로 external IP도 함께 제공이 되기 때문에 바로 서비스에 이용할 수도 있다. (아직은 404가 뜨겟지?)



5. 접속을 한번 해보자. 앞에서 말한것처럼 각 인스턴스는 기본적으로 2개의 firewall이 기본 등록이 되어 있다. 그 중 하나는 ssh 이다. 그렇기 때문에 쉽게 접속 할 수 있다.

$ gcutil ssh ins1
....
[계정정@ins1 ~]$ 

6. 간단한 apache page 확인해보기 

me@ins1$ sudo yum install httpd
... Installed size: 3.6 M Is this ok [y/N]: y me@ins1$ sudo service httpd start Starting httpd:

실제 : http://23.236.63.6


7. 어때요 참 쉽죠?

bitnami MEAN stack announce for OSX(mac)

Cloud 2013.11.16 02:00 posted by dev.bistro

dzone 에서 흥미로운 기사가 떳다. LAMP 와 달리 아직 MEAN스택은 단어 하나의 통합 인스톨 솔루션은 없을 줄 알았는데, 발표가 되었다. ( 링크 : http://blog.bitnami.com/2013/11/mean-stack-for-bitnami-mongodb-express.html )

nativie installer 와 VM   2가지 방법으로 제공해주는데 기존에 깔려 있던 얘들과 충돌을 피하기 위해 VirtualBox로 설치를 시작하였다. (AWS 이 가장 편하겠지만... 지금도 돈이 찔끔 찔끔 나가고 있는지라, 조금 고생하더라도 local VM을 이용하는걸로 결정했다 ㅠ)

#1. https://bitnami.com/stack/mean/virtual-machine  에서 [Download] 한다. 현재 파일 명은  [ bitnami-meanstack-2.4.8-0-ubuntu-12.04.zip ] 이다.
압축을 풀어서 보면 Virtual 4.X 버전을 이용하라고 한다.

#2. 이후 http://wiki.bitnami.com/Virtual_Appliances_Quick_Start_Guide#Virtual_Box 를 따라서 VirtualBox에 해당 VM instance를 설치한다.

#3. 기본 계정으로 접속한다. (게정의 username, password는 동일하고, 정보는 위의 링크에 있다)

#4. DB를 제외하곤 이미 다른것들이 모두 로컬에 설정되어 있는지라, 크게 위화감도... 장점도 느껴지지 못했다. (-_-; ) 


별 내용이 없네-_-




티스토리 툴바