집킨(zipkin)은 복잡한 시스템에서

마이크로 서비스의 상호 작용에 대한 개요를 파악하는 데 도움이 될 수 있다.






스프링 클라우드 Sleuth(https://cloud.spring.io/spring-cloud-sleuth/)는

스프링 부트 애플리케이션과 집킨을 통합할 수 있는 매우 쉬운 방법을 제공한다.


문서는 다음과 같다.

https://coe.gitbook.io/guide/log/sleuth



스프링 클라우드 Sleuth는 추적 ID와 범위(span) ID가 생성되고 통신 중에 전달되도록 한다.

그리고 추적 데이터를 집킨 서버로 전송한다.



스프링 클라우드에 zipkin 관련 프로젝트(spring-cloud-starter-zipkin)가 있다.


예시는 다음을 참조한다.

https://www.baeldung.com/tracing-services-with-zipkin





또한 스프링 클라우드 Sleuth를 사용해 추적 ID만 전송할 수도 있다(http://cloud.spring.io/spring-cloud-static/spring-cloud-sleuth/1.2.5.RELEASE/single/spring-cloud-sleuth.html#_only_sleuth_log_correlation).


로그에 추적 ID를 저장하고 있다면 요청에 대한 모든 로그 정보를 로그 분석에서 사용할 수 있다.

Posted by 김용환 '김용환'



open stack vm 인스턴스를 생성할 때 동일한 pm에 있으면, 

pm이 장애가 발생했을 때 같이 인스턴스가 종료되기 때문에 위험할 수 있다. 


같은 pm 장비에 vm 장비가 없도록 하는 것을 anti-affinity라는 정책인데. 그룹(group)에만  적용할 수 있다.

인스턴스 사용하는 방법은 다음과 같다.




$ openstack server group list


+--------------------------------------+--------------------+---------------+

| ID                                                           | Name                       |         Policies      |

+--------------------------------------+--------------------+---------------+

+--------------------------------------+--------------------+---------------+





anti-affinity 정책을 포함한 그룹을 추가한다.


$ openstack server group create stats-tsdb-read --policy anti-affinity


+----------+--------------------------------------+

| Field    | Value                                |

+----------+--------------------------------------+

| id       | 1cd7538d-23c4-493e-8684-3bf46c440864 |

| members  |                                      |

| name     | stats-tsdb-read                      |

| policies | anti-affinity                        |

+----------+--------------------------------------+





이제 stats-tsdb-read group으로 서버를 추가한다. 


$ openstack server create --flavor large --image ubuntu-16.04 --availability-zone US_WEST --hint group=1cd7538d-23c4-493e-8684-3bf46c440864 stats-tsdb-01



$ openstack server create --flavor large --image ubuntu-16.04 --availability-zone US_WEST --hint group=1cd7538d-23c4-493e-8684-3bf46c440864 stats-tsdb-02





만약 잘못만들었다면 다음과 같이 그룹을 삭제한다.


$ openstack server group delete stats-tsdb


Posted by 김용환 '김용환'

마이크로 서비스에서는 json 로그로 저장되도록 하는 것이 중요한데..

logback에 json 출력 기능이 있다. 



logstash logback 라이브러리를 쓰는 것도 있지만.. (아래 링크 참조)


http://knight76.tistory.com/entry/log-log4j%EC%97%90%EC%84%9C-json-%EB%A1%9C%EA%B7%B8%EB%A5%BC-%EC%B6%9C%EB%A0%A5%ED%95%98%EB%8F%84%EB%A1%9D-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0



 spring boot의 logback에도 json 출력한다.



<?xml version="1.0" encoding="UTF-8"?>

<configuration>

<include resource="org/springframework/boot/logging/logback/defaults.xml" />

<property name="LOG_FILE"

value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/}spring.log}" />

<include resource="org/springframework/boot/logging/logback/console-appender.xml" />


<appender name="JSON_FILE"

class="ch.qos.logback.core.rolling.RollingFileAppender">

<file>${LOG_PATH}/order-${PID}.json</file>

<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">

<fileNamePattern>order-${PID}.json.%d{yyyy-MM-dd}.gz</fileNamePattern>

<maxHistory>7</maxHistory>

</rollingPolicy>

<encoder

class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">

<providers>

<arguments />

<stackTrace />

<timestamp>

<timeZone>UTC</timeZone>

</timestamp>

<pattern>

<pattern>

{

"severity": "%level",

"service": "order",

"pid": ${PID:-},

"thread": "%thread",

"logger": "%logger",

"message": "%message"

}

</pattern>

</pattern>

</providers>

</encoder>

</appender>

<root level="INFO">

<appender-ref ref="JSON_FILE" />

<appender-ref ref="CONSOLE" />

</root>

</configuration>

Posted by 김용환 '김용환'


elasticsearch에 /를 포함하는 id를 저장하고 싶을때..

/를 사용하면 path 구분자에 걸린다. 


따라서 url에 / 대신 %2F를 사용하면 정상적으로 작동한다.




curl -X PUT "es.google.io:9200/google-host-staging/host/service.dos%2F0" -H 'Content-Type: application/json' -d'

{

   "service":"0",

   "host":"service.dos"

}

'






Posted by 김용환 '김용환'





상태가 없는 애플리케이션은 클러스터의 다른 노드에서 간단히 재시작할 수 있지만,,


상태가 있어야 하는 쿠버네티스 애플리케이션은 어떻게 구축할까?



쿠버네티스는 상태를 갖는 애플리케이션을 처리할 수 ​​있다.  이를 통해 장애 안정성 및 로드 밸런싱을 용이해진다.





애플리케이션이 상태를 갖는다면 즉, 도커 볼륨에 특정 데이터를 저장해야 한다면, 


애플리케이션이 실행되는 각 노드에서 필요한 도커 볼륨을 사용할 수 있어야 한다. 




따라서 상태를 갖는 애플리케이션의 처리가 더욱 복잡해진다. 



그래서 쿠버네티스는 영구 볼륨(persistent volumes)(https://kubernetes.io/docs/concepts/storage/persistent-volumes/)과 



상태 저장 셋(stateful sets)(https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#stable-storage)을 제공한다.







또 다른 방법은 operators(https://coreos.com/operators)을 사용하는 것이다. 


operators을 사용하면 상태를 갖는 애플리케이션을 자동으로 설치할 수 있다. 



예를 들어 프로메테우스(Prometheus) operator(https://github.com/coreos/prometheus-operator)가 있다. 


프로메테우스 operator는 쿠버네티스 클러스터에 모니터링 시스템인 프로메테우스를 설치할 수 있다. 


또한 Prometheus, ServiceMonitor, Altermanager와 같은 프로메테우스 컴포넌트에 대한 쿠버네티스 자원을 소개하며 프로메테우스 컴포넌트를 포드, 서비스, 배포 대신 쿠버네티스 설정으로 사용할 수 있다. 



프로메테우스 operator는 프로메테우스가 모니터링 데이터를 저장하는 방법을 결정해 핵심 과제를 해결한다.

Posted by 김용환 '김용환'



마라톤(marathon)은 url 상태 체크가 있는데. traffic이 많이 오면  health check 실패되어 자동 실행되고 그런게 좀 있었는데.


쿠버네티스는 url 헬스 체크외에 파일 체크 기능이 생겼다..


https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/




쿠버네티스는 Liveness Probe(https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)를 통해 포드의 장애를 파악한다. 


사용자 지정 Liveness Probe를 사용하여 애플리케이션의 요구에 맞춰 컨테이너가 새로 시작되는 시점을 결정할 수 있다.



한편,


Readiness Probe(https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#define-readiness-probes)는 컨테이너가 요청을 처리할 수 ​​있는지 여부를 나타낸다. 


예를 들어 애플리케이션이 많은 양의 데이터를 처리하거나 아직 완전히 시작되지 않은 경우에 요청이 블럭되면 Readiness Probe는 애플리케이션의 상태를 쿠버네티스에 알린다. 


Liveness Probe와는 달리 Readiness Probe가 실패했다고 해서 컨테이너가 재시작되지 않는다. 


쿠버네티스는 일정 시간 후에 포드가 요청을 처리할 수 있는 지 여부를 Readiness Probe를 통해 신호를 보낸다고 가정한다.





<Liveness Probe>


ports:

- name: liveness-port

  containerPort: 8080

  hostPort: 8080


livenessProbe:

  httpGet:

    path: /healthz

    port: liveness-port

    


<Rediness Probe>

    

readinessProbe:

  exec:

    command:

    - cat

    - /tmp/healthy

  initialDelaySeconds: 5

  periodSeconds: 5

Posted by 김용환 '김용환'

authz, authn

영어앤영문권 2018.08.10 10:31

authorization, authentication 길게 얘기하니까 발음도 그렇구 단어도 비슷하다



축약어 있나 확인해봤더니  authz, authn이라 한다.


출처 


https://en.wikipedia.org/wiki/Web_API_security


AUTHN (authentication) and AUTHZ (authorization)

Posted by 김용환 '김용환'


elasticsearch5.4에서 _id만 저장하는 테스트를 진행했다.


 testcase 시나리오는 다음과 같다.


index를 생성한다.

_id만 저장한다.

_id를 읽는다. 정상적이면 성공, 그러나 실패



성공한 것처럼 보인다... 그러나, 실제 curl로 _id를 document를 요청하면 데이터는 없다고 나온다!!


(사실 문법 에러는 아니지만,, 저장은 안해.. 라는 것으로 보인다)



따라서 아무리 간단한 데이터를 저장하더라도 _id와 field 데이터 하나 정도는 저장해야 디스크에 저장하길 바란다.

(당연하지만..)






Posted by 김용환 '김용환'



아래와 같은 toMap 코드를 사용하면 NPE 에러가 발핸다.


    final Map<String, Object> valueMap = pingFailedHosts

        .stream()

        .collect(Collectors.toMap(c->c, null));

        


toMap 메소드는 KeyMapper와 ValueMapper를 전달하는 형태인데. 

public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}


toMap 원형 함수를 보면, mergeFunction , mapSupplier가 추가된다. 

public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
}


https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html



toMap 메소드의 매개변수를 보면, mergeFunction이 Map.merge()와 연관되어 있음을 알 수 있다. 

Parameters:
keyMapper - a mapping function to produce keys
valueMapper - a mapping function to produce values
mergeFunction - a merge function, used to resolve collisions between values associated with the same key, as supplied to Map.merge(Object, Object, BiFunction)
mapSupplier - a function which returns a new, empty Map into which the results will be inserted







코드를 따라가 들어가면


private static <K, V, M extends Map<K,V>>
BinaryOperator<M> mapMerger(BinaryOperator<V> mergeFunction) {
return (m1, m2) -> {
for (Map.Entry<K,V> e : m2.entrySet())
m1.merge(e.getKey(), e.getValue(), mergeFunction);
return m1;
};
}


m1.merge() 메소드가 바로 Map.merge() 메소드이다.


여기 코드 보면, value 값이 not null이어야 한다. 


default V merge(K key, V value,
BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
Objects.requireNonNull(remappingFunction);
Objects.requireNonNull(value)
;
V oldValue = get(key);
V newValue = (oldValue == null) ? value :
remappingFunction.apply(oldValue, value);
if(newValue == null) {
remove(key);
} else {
put(key, newValue);
}
return newValue;
}


코드의 주석이나, 오라클 javadoc 문서를 보면 역시 NPE를 발행하는 코드임을 확인할 수 있다.


NullPointerException - if the specified key is null and this map does not support null keys or the value or remappingFunction is null





따라서 key나 value의 값이 null일 확률이 높다면, toMap()를 사용하지 말아야 한다.


아래와 같이 naive하게 개발하는 것이 좋다.


    final Map<String, String> valueMap2 = Arrays.asList("cacti.google.com", "sun.google.com")

        .stream()

        .collect(HashMap::new, (m,v)->m.put(v, null), HashMap::putAll);




Posted by 김용환 '김용환'


javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. 에러가 발생하면 라이브러를 추가한다.


2018-07-30T16:04:06,790 DEBUG o.s.b.f.s.DefaultListableBeanFactory.doCreateBean(539) - Eagerly caching bean 'mvcValidator' to allow for resolving potential circular references

2018-07-30T16:04:06,796 DEBUG o.s.b.f.s.DefaultListableBeanFactory.invokeInitMethods(1670) - Invoking afterPropertiesSet() on bean with name 'mvcValidator'

2018-07-30T16:04:06,797 DEBUG o.h.v.i.e.r.DefaultTraversableResolver.detectJPA(70) - Cannot find javax.persistence.Persistence on classpath. Assuming non JPA 2 environment. All properties will per default be traversable.

2018-07-30T16:04:06,798 DEBUG o.s.v.b.OptionalValidatorFactoryBean.afterPropertiesSet(43) - Failed to set up a Bean Validation provider

javax.validation.ValidationException: HV000183: Unable to initialize 'javax.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead

        at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:102)

        at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.<init>(ResourceBundleMessageInterpolator.java:45)

        at org.hibernate.validator.internal.engine.ConfigurationImpl.getDefaultMessageInterpolator(ConfigurationImpl.java:423)

        at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.afterPropertiesSet(LocalValidatorFactoryBean.java:264)


Caused by: java.lang.NoSuchMethodError: javax.el.ExpressionFactory.newInstance()Ljavax/el/ExpressionFactory;

        at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:98)

        ... 46 common frames omitted




spring boot에서는 아래와 같이 build.gradle 파일을 수정한다. 


    compile('javax.el:javax.el-api:3.0.0')

    compile('org.glassfish:javax.el:3.0.0')




pom.xml 파일을 수정한다.
<dependency>
    <groupId>javax.el</groupId>
    <artifactId>javax.el-api</artifactId>
    <version>3.0.0</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.0</version>
</dependency>



Posted by 김용환 '김용환'