블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

로그 시스템 #2- 자바 로그 & JSON 로그 포맷

조대협 (http://bcho.tistory.com)


앞 글에서 간단하게 자바 로깅 프레임워크에 대해서 알아보았다. 그러면 앞에서 추천한 slf4j와 log4j2로 실제 로깅을 구현해보자

SLF4J + log4j2

메이븐 프로젝트를 열고 dependencies 부분에 아래 의존성을 추가한다. 버전은 최신 버전을 확인하도록 한다. artifactid가 log4j-slf4j-impl 이지만, log4j가 아니라 log4j2가 사용된다.


<dependency>

<groupId>org.apache.logging.log4j</groupId>

<artifactId>log4j-slf4j-impl</artifactId>

<version>2.11.2</version>

</dependency>


다음 log4j2의 설정 정보 파일인 log4j2.properties 파일을 src/main/resources 디렉토리 아래에 다음과 같이 생성한다. Appender나, Layout등 다양한 정보 설정이 있지만 그 내용은 나중에 자세하게 설명하도록 한다.


appenders=xyz


appender.xyz.type = Console

appender.xyz.name = myOutput

appender.xyz.layout.type = PatternLayout

appender.xyz.layout.pattern = [MYLOG %d{yy-MMM-dd HH:mm:ss:SSS}] [%p] [%c{1}:%L] - %m%n


rootLogger.level = info


rootLogger.appenderRefs = abc


rootLogger.appenderRef.abc.ref = myOutput


그리고 아래와 같이 코드를 만든다.

LoggerFactory를 이용해서 Logger를 가지고 온다. 현재 클래스 명에 대한 Logger 를 가지고 오는데, 위의 설정 파일을 보면 rootLogger만 설정하였기 때문에, rootLogger가 사용된다.

package com.terry.logging.helloworld;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;



public class App

{

   private static Logger log = LoggerFactory.getLogger(App.class);

   public static void main( String[] args )

   {

       System.out.println( "Hello World!" );

       

       log.info("Hello slf4j");

   }

}



가저온 logger를 이용해서 log.info로 로그를 출력한다.

콘솔로 출력된 로그는 아래와 같다.

[MYLOG 19-Mar-18 23:07:01:373] [INFO] [App:71] - Hello slf4j


JSON 포맷으로 로그 출력

근래에는 시스템이 분산 구조를 가지고 있기 때문에 텍스트 파일로 남겨서는 여러 분산된 서비스의 로그를 모아서 보기가 어렵다. 그래서, 이런 로그를 중앙 집중화된 서버로 수집 및 분석하는 구조를 가지는데, 수집 서버에서는 이 로그들을 구조화된 포맷으로 저장하는 경우가 일반적이다. 각 로그의 내용을 저장 구조의 개별 자료 구조(예를 들어 테이블의 컬럼)에 맵핑해서 저장하는데, 이를 위해서는 로그가 JSON,XML 또는 CSV와 같은 형태로 구조화가 되어 있어야 한다.

이런 구조화된 로그를 structured logging 이라고 한다. 로그 엔트리 하나를 JSON에 포함해서 출력하는 방법에 대해서 알아본다.

slf4j + logback

SLF4 + logback을 이용하여 레이아웃을 JSON으로 출력하는 코드이다.


package com.terry.logging.logback;


import java.util.Map;

import java.util.TreeMap;


import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


import com.fasterxml.jackson.core.JsonProcessingException;

import com.fasterxml.jackson.databind.ObjectMapper;


public class App

{

   private static Logger log = LoggerFactory.getLogger(App.class);

   public static void main( String[] args ) throws JsonProcessingException

   {


       log.info("hello log4j");

   }

}


pom.xml에 아래와 같이 logback과 json 관련 dependency를 추가한다.


<dependencies>

<dependency>

<groupId>ch.qos.logback</groupId>

<artifactId>logback-classic</artifactId>

<version>1.1.7</version>

</dependency>


<dependency>

<groupId>ch.qos.logback.contrib</groupId>

<artifactId>logback-json-classic</artifactId>

<version>0.1.5</version>

</dependency>


<dependency>

<groupId>ch.qos.logback.contrib</groupId>

<artifactId>logback-jackson</artifactId>

<version>0.1.5</version>

</dependency>


<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.9.3</version>

</dependency>

</dependencies>



마지막으로 src/main/resources.xml 파일을 아래와 같이 작성한다.  

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

<configuration>

   <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">

       <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">

           <layout class="ch.qos.logback.contrib.json.classic.JsonLayout">

               <timestampFormat>yyyy-MM-dd'T'HH:mm:ss.SSSX</timestampFormat>

               <timestampFormatTimezoneId>Etc/UTC</timestampFormatTimezoneId>


               <jsonFormatter class="ch.qos.logback.contrib.jackson.JacksonJsonFormatter">

                   <prettyPrint>true</prettyPrint>

               </jsonFormatter>

           </layout>

       </encoder>

   </appender>


   <root level="debug">

       <appender-ref ref="stdout"/>

   </root>

</configuration>


아래는 출력 결과이다. message 필드에 로그가 출력 된것을 볼 수 있다.


{

 "timestamp" : "2019-03-19T07:24:31.906Z",

 "level" : "INFO",

 "thread" : "main",

 "logger" : "com.terry.logging.logback.App",

 "message" : "hello log4j",

 "context" : "default"

}


slf4j + log4j2

다음은 slft4+log4j2 를 이용한 예제이다.  logback과 크게 다르지는 않다.

아래와 같이 pom.xml 의 dependencies에 아래 내용을 추가하자. json layout은 jackson을 사용하기 때문에 아래와 같이 jackson에 대한 의존성도 함께 추가한다.


<dependency>

<groupId>org.apache.logging.log4j</groupId>

<artifactId>log4j-slf4j-impl</artifactId>

<version>2.11.2</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-core</artifactId>

<version>2.7.4</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-databind</artifactId>

<version>2.7.4</version>

</dependency>

<dependency>

<groupId>com.fasterxml.jackson.core</groupId>

<artifactId>jackson-annotations</artifactId>

<version>2.7.4</version>

</dependency>


다음 아래와 같이 log4j2.properties 파일을 src/main/resources 폴더에 저장한다.


status = info


appender.ana_whitespace.type = Console

appender.ana_whitespace.name = ana_whitespace

appender.ana_whitespace.layout.type = JsonLayout

appender.ana_whitespace.layout.propertiesAsList = false

appender.ana_whitespace.layout.compact = false

appender.ana_whitespace.layout.eventEol = true

appender.ana_whitespace.layout.objectMessageAsJsonObject = true

appender.ana_whitespace.layout.complete= true

appender.ana_whitespace.layout.properties= true


rootLogger.level = info

rootLogger.appenderRef.ana_whitespace.ref = ana_whitespace


위에 보면 layout.type을 JsonLayout으로 지정하였다. 기타 다른 필드에 대한 정보는

정보는 https://logging.apache.org/log4j/2.0/manual/layouts.html 를 참고하기 바란다.


그리고 아래와 같이 코드를 이용해서 info 레벨의 로그를 출력해보자

package com.terry.logging.jsonlog;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;



public class App

{

private static Logger log = LoggerFactory.getLogger(App.class);

   public static void main( String[] args )

   {

       

       log.info("Hello json log");

       log.error("This is error");

       log.warn("this is warn");

   }

}


코드를 컴파일 하고 실행하면 아래와 같은 형태로 로그가 출력된다. 로그 출력 형태는 logback과는 많이 차이가 있다.


[

{

 "thread" : "main",

 "level" : "INFO",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "Hello json log",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923302,

   "nanoOfSecond" : 38337000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

}

, {

 "thread" : "main",

 "level" : "ERROR",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "This is error",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923302,

   "nanoOfSecond" : 109170000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

}

, {

 "thread" : "main",

 "level" : "WARN",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "this is warn",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923302,

   "nanoOfSecond" : 109618000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

}


]


json을 여러가지 포맷으로 출력할 수 있다. 위의 로그를  잘보면 로그 시작과 끝에 json 포맷을 맞추기 위해서 “[“와 “]”를 추가하고, 로그 레코드 집합당 “,”로 레코드를 구별한것을 볼 수 있다. 만약에 “[“,”]”를 로그 처음과 마지막에서 제거하고, 로그 레코드 집합동 “,”를 제거하고 newline으로만 분류하고 싶다면 log4j2.properties 파일에서 appender.ana_whitespace.layout.complete = false로 하면 된다.

아래는 layout.complete를 false로 하고 출력한 결과 이다.


{ ←  이부분에 “[” 없음

 "thread" : "main",

 "level" : "INFO",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "Hello json log",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923722,

   "nanoOfSecond" : 98574000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

} ←  이부분에 콤마가 없음

{

 "thread" : "main",

 "level" : "ERROR",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "This is error",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923722,

   "nanoOfSecond" : 167047000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

}

{

 "thread" : "main",

 "level" : "WARN",

 "loggerName" : "com.terry.logging.jsonlog.App",

 "message" : "this is warn",

 "endOfBatch" : false,

 "loggerFqcn" : "org.apache.logging.slf4j.Log4jLogger",

 "instant" : {

   "epochSecond" : 1552923722,

   "nanoOfSecond" : 167351000

 },

 "contextMap" : { },

 "threadId" : 1,

 "threadPriority" : 5

} ←  이부분에 “]” 없음


그리고 로그파일을 보는데, JSON의 경우에는 위와 같이 각 element 마다 줄을 바꿔서 사람이 읽기 좋은 형태이기는 하지만, 대신 매번 줄을 바꾸기 때문에 검색이 어려운 경우가 있다. 그래서 로그 레코드 하나를 줄 바꿈 없이 한줄에 모두 출력할 수 있도록 할 수 있는데, appender.ana_whitespace.layout.compact = true로 주면 된다. 아래는 옵션을 적용한 결과 이다.

{"thread":"main","level":"INFO","loggerName":"com.terry.logging.jsonlog.App","message":"Hello json log","endOfBatch":false,"loggerFqcn":"org.apache.logging.slf4j.Log4jLogger","instant":{"epochSecond":1552923681,"nanoOfSecond":430798000},"contextMap":{},"threadId":1,"threadPriority":5}

{"thread":"main","level":"ERROR","loggerName":"com.terry.logging.jsonlog.App","message":"This is error","endOfBatch":false,"loggerFqcn":"org.apache.logging.slf4j.Log4jLogger","instant":{"epochSecond":1552923681,"nanoOfSecond":491757000},"contextMap":{},"threadId":1,"threadPriority":5}

{"thread":"main","level":"WARN","loggerName":"com.terry.logging.jsonlog.App","message":"this is warn","endOfBatch":false,"loggerFqcn":"org.apache.logging.slf4j.Log4jLogger","instant":{"epochSecond":1552923681,"nanoOfSecond":492095000},"contextMap":{},"threadId":1,"threadPriority":5}



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

로그 시스템 #1 - 자바 로그 프레임웍

조대협 (http://bcho.tistory.com)

로그 시스템

로그 시스템은 소프트웨어의 이벤트를 기록 함으로써, 소프트웨어 동작 상태를 파악하고 문제가 발생했을때 이 동작 파악을 통해서 소프트웨어의 문제를 찾아내고 해결하기 위해서 디자인 되었다.

주로 로그 파일이라는 형태로 하나의 파일에 이벤트들을 기록하였다.


그러나 소프트웨어 스택이 OS, 미들웨어, 사용자 애플리케이션 (자바나 파이썬등으로 구현된 애플리케이션)으로 점점 다중화되고 시스템이 대형화 되면서 한대가 아니라 여러대의 서버에 로그를 기록하고 또한 마이크로 서비스 아키텍처로 인하여 서버 컴포넌트가 분산됨에 따라서 로그를 수집해야할 포인트가 많아지게 되었다. 이로 인해서 로그 시스템이 분산 환경을 지원해야 할 필요가 되었고, 단순히 파일로 로그를 기록하는 것만으로는 이러한 여러 시스템과 다중 계층에 대한 모니터링이 불가능하게 되었다.


또한 데이터 분석의 중요성이 대두됨에 따라서, 에러등의 동작 파악성의 로그 뿐만 아니라 사용자의 액티버티를 수집하여 데이터 분석에 사용하기 위해서 데이터 수집 역시 로그 시스템을 통하기 시작하였다.


그래서 몇개의 글에 걸쳐서 좋은 로그 시스템을 개발하기 위한 아키텍처에 대해서 설명하고자 한다.

좋은 로그 시스템이란

먼저 좋은 로그 시스템의 기본 개념을 정의 해보면 다음과 같다.

  • 로그 메시지는 애플리케이션의 동작을 잘 이해할 수 있도록 충분히 구체적이어야 한다.

  • 로그 메시지를 기록하는데 성능 저하가 없어야 한다.

  • 어떤 배포 환경이라도 로그를 수집하고 저장할 수 있도록 충분히 유연해야 한다. (분산 환경 지원, 대용량 데이타 지원등)

자바 로깅 프레임워크

각 프로그래밍 언어마다 고유의 로깅 프레임워크을 지원하지만, 특히 자바의 경우에는 그 프레임웍 수가 많고 발전된 모델이 많아서 자바 프레임워크를 살펴보고 넘어가고자 한다.  

자바는 역사가 오래된 만큼 많은 로깅 프레임웍을 가지고 있다. log4j, logback, log4j2,apache common logging, SLF4J 등 다양한 프레임워크 들이 있는데, 그 개념과 장단점을 알아보도록 하자.

SLF4J

SLF4J는 (Simple Logging Facade for Java)의 약자로 이름이 뜻하는 것과 같이 로깅에 대한 Facade 패턴이다. SLF4J는 자체가 로깅 프레임웍이 아니라, 다양한 로깅 프레임웍을 같은 API를 사용해서 접근할 수 있도록 해주는 추상화 계층이다. 그래서 다른 로그프레임웍과 같이 사용해야 하는데, 보통 Log4J, Logback, Log4J2등이 많이 사용된다. 즉 애플리케이션은 SLF4J API 인터페이스를 통해서 호출하지만, 실제로 호출되는 로깅 프레임웍은 다른 프레임웍이 호출된다는 이야기이다. 이렇게 추상화를 통해서 용도와 목적에 맞게 다른 로깅 프레임워크 으로 쉽게 전환이 가능함은 물론이고, 로깅에 필요한 코드들을 추상화해주기 때문에, 훨씬 쉽고 간단하게 로깅이 가능하다. apache common logging 역시, SLF4J와 같이 다른 로깅 프레임워크 들을 추상화 해주는 기능을 제공한다.



<그림 : SLF4J 가 다른 로깅 프레임웍을 추상화 하는 개념도 >

출처 source


그러나 SLF4J 이전에 개발된 레거시 시스템들의 경우에는 이러한 추상화 계층이 없어서 로그 프레임웍을 변경하고 있기 때문에 로깅 프레임웍을 교체하기가 어렵다. 이런 상황을 해결하기 위해서 SLF4J는 기존 로그 프레임웍에 대한 브릿지를 제공한다. 예를 들어 log4J로 개발된 로깅을 브릿지를 이용해서 SLF4J를 사용하도록 전환할 수 있다. 이런 구조는 레거시 로깅 시스템을 사용해서 개발된 시스템에 대해서, 로그 프레임웍에 대한 코드를 변경하지 않고, 뒷단에 로그 프레임웍을 변경할 수 있게 해주기 때문에, 로깅 프레임웍에 대한 마이그레이션을 쉽게 해준다.



<그림 : SLF4J 브릿지를 이용해서, 기존 로그 시스템을 연동 하는 개념도 >


자바 로깅 프레임워크

자바 로그 프레임웍에는 여러가지 종류가 있지만 그중에서 대표적을 사용되는 로그 프레임웍은 log4j,logback,log4j2 세가지 이다.

Log4J

Log4J는 이 중에서 가장 오래된 로그프레임웍으로 로그 프레임웍에 대한 초반 개념을 설정했다고 볼 수 있다. 현재는 개발이 중지되고, Log4J2로 새로운 버전으로 변경되었다.

Logback

아마 현재 국내에서 가장 널리 많이 사용되고 있는 로그 프레임워크일것이다. Log4J 개발자가 개발한 로그 프레임워크로 주로 Log4J 성능 부분에 대한 개선 작업이 많이 이루어 졌다. SLF4J와 네이티브로 연동이 가능하다.

Log4J2

가장 근래에 나온 프레임워크로 Logback 보다 후에 나오고, 가장 빠른 성능을 제공한다. Logback과 SLF4J사이의 연동 문제를 해결하였으며 비동기 로깅 ( asynchronous logging ) 을 제공하여, 특히 멀티 쓰레드 환경에서 높은 성능을 제공한다.



(source : https://logging.apache.org/log4j/2.x/performance.html )


또한 근래의 로깅 시스템들은 로그를 파일로 기록하기 보다는 ELK(Elastic Search)나 Kafka 등 외부 시스템으로 로그를 전송하여 모으는 형태를 많이 취하기 때문에 이에 대한 연동을 Appender를 통해서 제공한다.


제공되는 Appender는 다음과 같다.

  • Console

  • File, RollingFile, MemoryMappedFile

  • Flume, Kafka, JDBC, JMS, Socket, ZeroMQ

  • SMTP (emails on errors, woo!)

  • … much more


만약에 새로운 시스템을 개발한다면, Logback 보다는 그 다음 세대인 격인 Lob4j2를 사용하는 것을 권장한다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 2019.04.03 09:29  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

개발자용 노트북 구입기

조대협 (http://bcho.tistory.com)


집에서 개발 공부와, 테스트를 위해서 개인 노트북이 필요했는데, 아무래도 업무와 개인 공부를 분리하는것이 좋을것 같아서, 얼마전 부터 개인 노트북을 사용하기 시작했다.


여러가지 고민을 하다가, 딸에게 주었던 한성 U35S를 돌려받고, 딸에게는 ASUS 라이젠 노트북을 사주었다.



I7에 16G 메모리, 128G SSD로 나쁘지 않은 스펙이었지만, 대략 7년정도 된 노트북이라 우분투를 깔고 사용은 했지만, 일반적인 문서 작업이나 코딩들도 가능했지만, 아무래도 서버를 여러개 올리거나 컴파일을 많이 거니, 팬 소리가 너무 귀에 거슬리기 시작했고, 이참에 노트북을 하나 바꿔야 하겠다하고 개발용 노트북을 찾기 시작했다.


아무래도 개발용으로 사용할 노트북이기 때문에, 성능이 좋아야 하고, 쿠버네티스등 여러 서버를 띄울 것이기 때문에, 메모리가 충분해야 한다 (32G), 디스플레이는  개인적으로 IPS를 선호하고, IO는 nvme 디스크를 정해놓고 찾기 시작했다.


고 스펙으로 검색을 하다보니, 게이밍 노트북이 주로 올라왔는데, 이런 노트북은 I7-8550H (6코어)를 지원하는데다가, GTX-1050 TI나 1060 GPU를 지원하였다. 아무래도 GPU 지원만 잘된다면, 텐서플로우나 케라스같은 머신러닝 작업에 적합할것이라고 생각했다.


카페에서도 가끔 코딩을 하기 때문에 노트북이 너무 게이밍 스러우면 안되고, 그래서 좁혀진것이 레너보 Y530과 한성 TFG155 모델이었다.


예전과 다르게 요즘 랩탑들은 우분투 호환이 안되는 것들이 생각보다 많고 멀티 부트가 안되는 모델들이 많다. 특히 일반 노트북들은 크게 문제가 없지만 GTX GPU가 들어가는 경우에는 듀얼 부트가 문제가 많았다.



<그림. 레노버 Y530>


Y530은 다양한 모델 선택이 가능하고, 디자인도 괜찮은 편이고 냉각팬도 많이 달려있지만, 해외 커뮤니티를 살펴보니 우분투 설치가 쉽지는 않았지만, 여러 설정값을 조정하면 설치가 가능하다는 것을 찾았지만, 듀얼 부팅이 안되는 문제점이 있었다. 그리고 IPS 패널이기는 하지만  3 종류의 IPS 패널을 지원하는데, 60HZ, 250 nits 모델은 화면이 누렇다는 이야기가 많았다. 가격도 좋았지만, 결국 윈도우만 돌리고 있을거 같아서 아쉽지만 패스




<그림. 한성 TFG155>


다음 모델은 TFG155  모델인데, 알루미늄 상판에 디자인되 괜찮고, 스펙도 훌륭하다. 그러나 역시 누런 IPS 문제가 고질적인데, 이건 순전히 뽑기 운에 달려있고, 무엇보다 리눅스 설치에 성공했다는 사용자가 없었다. 특히 국내 내수용 모델이기 때문에 해외에서 몬가 관련된 문서도 없었다.  발열 문제도 꽤 심각한걸로 보이고, U35S를 기존에 사용해본 결과, 내구성에 문제가 있다고 판단하고 패스

(2019년 4/13일 추가. 페이스북에 보니, 한성 장비에 우분투 18.04 LTS 설치하신분 글이 올라왔다. 무지 삽질은 하셨다고 하지만 일단은 가능한것으로 알려졌으니 추가)


6 코어 CPU를 가지고 싶었지만, 개발용 장비가 필요한거지 우분투 드라이버나 커널설정에 시간을 보내는게 목적이 아닌만큼, 6 코어를 포기하고 4 코어 제품을 살펴보기 시작하였다.

ASUS 노트북이 가격도 저렴하고 디자인도 깔끔해서 물망에 오르기는 했지만, nvme를 지원하지 않고, 16G 메모리까지 밖에 확장이 안되는 이유로 패스.


리누즈 토발즈가 DELL XPS 시리즈를 사용한다고 해서 살펴보았는데, 역시나이다.

디자인도 깔끔하고 9750 CPU를 지원하고, 우분투 설치도 보장이 된다.


결국은 가격이 문제 260만원이다.. 아.. 가지고 싶지만. 다른 랩탑보다 100~150만원은 비싼듯.. 이 돈이면 애들 학원을 하나 더 보내지..

그래서 하급 기종을 살펴보니 CPU를 8300까지 내려고 200만원..




아무리 스펙을 내려봐도 답이 안나온다. 그나마 미국에서는 가격이 저렴한 모델도 있고 Developer Edition이라고 따로도 있는데.. 한국에는 수입이 안된다. (DELL 관계자 여러분 Developer Edition좀 수입 해주시면 안되실까요? ) 개발자 전용 모델이라 스펙이 정말 개발용으로 딱이다. 미국 출장가서 사서 핸드캐리할까도 생각했지만, 글로벌 AS 가 안된다고 들어서.. 그냥 포기..


그래서 우분투에서 인증 받은 노트북을 찾아보니, 그나마 인스피리언 7580 시리즈가 눈에 들어왔다.

삼성, LG를 제외한 한성이나 다른 브렌드들은 AS가 안좋다는 말이 많은데, DELL은 AS가 좋기로 소문이 나있었고, 공식 우분투 지원이라니 괜히 모험을 할 필요가 없었다.


<그림. Ubuntu 18.04 LTS 지원 델 노트북 목록 >

https://certification.ubuntu.com/desktop/models/?query=&category=Laptop&level=Any&release=18.04+LTS&vendors=Dell

i7 코어에, 32G 메모리 확장 가능, NVME 탑재, IPS 디스플레이, 알루미늄 바디. 덤으로 MX150 GPU까지. 고성능 딥러닝 학습은 불가능하겠지만, 아쉬운데로 쓸 수 있을듯.

그리고 DELL 은 공식적으로 우분투를 지원하는지라, 윈도우와 듀얼 부트 가이드까지 공식적으로 지원한다. https://www.dell.com/support/article/us/en/04/sln301754/dell-pc%EC%97%90%EC%84%9C-ubuntu-%EB%B0%8F-windows-8-%EB%98%90%EB%8A%94-10%EC%9D%84-%EB%93%80%EC%96%BC-%EB%B6%80%ED%8C%85%EC%9C%BC%EB%A1%9C-%EC%84%A4%EC%B9%98-%ED%95%98%EB%8A%94-%EB%B0%A9%EB%B2%95?lang=ko


설치 스트레스가 훨씬 덜할듯. 네이버에 후기를 찾아보니.. 헐.. 이 노트북 후기는 거의 없다. 110~120만원대에서는 약간 포지셔닝이 애매할듯. 보통 유사 스펙이면 이것보다 가격이 살짝 낮은데.. 일반적인 사용자들이 메모리 32G에 NVME를 꼽을일은 거의 없다. 그정도 스펙이면 게이밍인데, 대부분 게이밍 노트북으로 넘어가서 GTX GPU를 쓰겠지... (국내 노트북 시장은 개발자 마켓들을 따로 안보나보다..)


<그림 DELL Inspiron 7580 핑크 버전. 한국은 안판다.>


거기다 행사 기간이라고 무선 마우스까지 덤이다. 이것도 제대로 된거 사려면 3~4만원은 줘야 하는데, 윈도우 라이센스는 집에 정품이 있기에 필요는 없지만 그래도 윈도 프로가 들어있다.

그런데 판매하는 제품은 메모리 16 G 옵션밖에 없고 디스크도 128 SSD + 1TB HDD 또는 512 G 메모리만 지원이 된다. 250G 정도면 딱인데…

메모리 스펙을 찾아보니, 대략 16G 삼성 정품으로 10만원 정도면 구입이 가능한데, 인터넷에서는 8G 추가하는데 10만원(??) 호갱인가??? 그리고, 256 삼성 nvme도 10만원이면 따로 구입이 가능하다.

128SSD + 1TB → 512 모델 업그레이드가 10만원 차이인데… 디스크 모자르면 직접 사서 바꿀것으로 생각하고, 그냥 16GB 메모리 두개만 추가해서 구입.. (남는 8G는 당근 마켓에 싸게 팔아버릴 요량..)


발열로 인하 소음도 걱정되고, 모니터와 같이 병행 사용할것이라서 잘만 스탠드도 2만원주고 업글하고,

코딩할때 화면이 적어서 모니터도 하나 구입하기로 하고, 32인치를 사고 싶었지만.. 책상이 좁고 가격 때문에 27인치 결정. IPS에 무결점 그리고 책상 두께만 많이 차지 않으면 괜찮아서, 무난하게 알파 스캔 모니터 주문. 결정적으로 모니터암을 달 수 없는 구조지만.. 그냥 싸니까..



이렇게 해서,

DELL 7580 I7, 8G 메모리, 128 nvme + 1TB 디스크 + 마우스는 위메프에서 할인 쿠폰이 나와서 다나와 보다 싸게 115만원

삼성 DDR4 16G 메모리 2개 = 22만원

27인치 알파스캔 모니터 = 25만원

잘만 쿨러 24000원


이렇게 해서 대략 165만원 정도. 32 기가, 27인치 IPS 모니터, 알루미늄 바디 노트북 세트로 이정도 가격이면 대단히 선방한듯..

다음에는 더 많이 벌어서. 리누즈 토발즈 처럼 XPS15 풀스펙 모델을 쓸 수 있기를 바라며..

(4/13 추가. 모니터 자체는 크게 문제는 없는데, 노트북은 15인치인데, 모니터가 27인치다 보니 둘이 글자 크기가 너무 난다. 27인치로 하려면 더 고해상도를 쓰거나 아니면 모니터를 24인치로 하는 선택이 더 좋았을듯)


===

업데이트 노트북이 도착해서 인스톨 작업을 진행하였다.

128G SSD에 우분투를 인스톨하고 윈도우 듀얼 붓을 사용하려니 윈도우 파티션이 이것저것 나눠지고 깔리고 복잡해서 용량이 모잘라서 결국은 삼성 EVO 970 PRO를 추가 주문하였다. (+20만원). 돈 아끼려다가.. 그냥 초반에 512 NVME SSD (10만원추가)를 주문하는 것을 추천한다.


다음 인스톨 과정인데, 바이오스에서 RAID를 제외하고 ACHI모드인가로 인식하면 인식은 잘 된다. 다음 윈도우를 다시 인스톨 해야 하는데, 델 사이트에서 제공하는 리커버리 윈도우즈 이미지를 사용하면, 설치가 안된다. (디스크에 대한 드라이버를 찾지 못한다. ) 한참을 씨름하다가 마이크로 소프트의 윈도우즈 정식 이미지를 다운 받아서 사용하니 바로 인식도 되고 한번에 인스톨이 된다. (https://www.microsoft.com/ko-kr/software-download/windows10ISO)


트랙패드, 마우스, 디스플레이 모두 완벽하게 작동한다. 단 돌다가 가끔 커널 패닉에 빠지면서 멈추는데..

이건 NVIDIA GPU와 충돌 때문에 발생한다. 소프트웨어 업데이트 메뉴에 가서 드라이버 업데이트 메뉴를 보면  NVIDIA  드라이버 업데이트가 가능하다.

업데이트 후 문제없이 작동한다.





'사는 이야기' 카테고리의 다른 글

개발자 코딩 노트북 구입기 (DELL Inspiron 7580) 우분투  (8) 2019.03.10
블로그 600만 돌파  (1) 2018.12.10
블로그 500만 돌파  (0) 2018.05.10
지난 1년 회고  (0) 2017.09.20
블로그 400만 돌파  (2) 2017.07.17
2016년 업무 종료....  (3) 2016.12.29
본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. skyer9 2019.03.12 10:10  댓글주소  수정/삭제  댓글쓰기

    헙. 구글 뉴스에 올라오셨네요.

  2. anonymous 2019.03.22 00:52  댓글주소  수정/삭제  댓글쓰기

    "결국은 가격이 문제 260만원이다.. 아.. 가지고 싶지만. 다른 랩탑보다 100~150만원은 비싼듯.. 이 돈이면 애들 학원을 하나 더 보내지.."

    같은 딸 둘 아빠로써 짠하네요..

  3. gwise 2019.04.02 13:34  댓글주소  수정/삭제  댓글쓰기

    일반 윈도우 OS에서 모바일과 웹/cs/DB관련 개발한다면 Y530 vs Inspiron7580 둘 중 어느게 좋을까요?

  4. 허브잎향기 2019.04.27 13:46  댓글주소  수정/삭제  댓글쓰기

    Y530 강추드립니다
    확장해서 2tb hdd 512 ssd 우분투 기반
    멀티 오에스해도 끄덕 없어요 ㄷㄷ

    매우 좋습다
    거기에 1060
    개발자분들 학습용으론 최고에요 두번쓰세여
    근데 땅크라서 못들고다녀요

  5. 엘르아시아 2019.05.20 10:26  댓글주소  수정/삭제  댓글쓰기

    제가 It 이제 배우고있는데 노트북이 필요할것같은데 무게는상관없이 좋은노트북좀 추천좀 해주실수있으실까여

  6. 하늘의사람 2019.10.15 00:47  댓글주소  수정/삭제  댓글쓰기

    발열 문제가 심하다던데 괜찮나요?ㅎㅎ

[팁] 쿠버네티스 StatefulSet에서 Headless 서비스를 이용한 Pod discovery


조대협 (http://bcho.tistory.com)


statefulset에서 데이타베이스와 같이 master,slave 구조가 있는 서비스들의 경우에는 service를 통해서 로드밸런싱을 하지 않고, service 를 통해서 로드 밸런싱을 하는 것을 잘 사용하지 않고 개별 pod의 주소를 알고 접속해야 한다. 그래서 개별 Pod의 dns 이름이나 주소를 알아야 한다.


Pod들은 DNS이름을 가질 수 는 있으나, {pod name}.{service name}.{name space}.svc.cluster.local 식으로 이름을 가지기 때문에, pod 를 DNS를 이용해서 접근하려면 service name이 있어야 한다. 그러나 statefulset에 의한 서비스들은 앞에서 언급하였듯이 쿠버네티스 service를  이용해서 로드밸런싱을 하는 것이 아니기 때문에, 로드밸런서의 역할은 필요가 없고, 논리적으로, pod들을 묶어줄 수 있는 service만 있으면 되기 때문에 headless 서비스를 활용한다. Headless 서비스를 이용하면, service 가 로드 밸런서의 역할도 하지 않고, 단일 IP도 가지지 않지만, 아래 그림처럼 nslookup을 이용해서, headless 서비스에 의해서 묶여진 Pod들의 이름도 알 수 있고




{pod name}.{service name}.{name space}.svc.cluster.local  이름으로, 각 pod 에 대한 접근 주소 역시 얻을 수 있다.




본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

도커 컨테이너 보안 취약점 스캔 도구 Anchore

조대협 (http://bcho.tistory.com)


근래에 쿠버네티스를 로컬환경에서  이것저것 테스트하다보니, 클라우드에 있는 기능 보다 오픈 소스 기능을 많이 보게 되는데, 빌드 파이프라인을 보다가 재미있는 오픈소스를 하나 찾아서 정리해놓는다.


컨테이너 이미지에 대한 보안 문제

쿠버네티스와 같은 컨테이너 오케스트레이션 솔루션에서 가장 보안 취약이 있는 곳 중의 하나는 컨테이너 이미지 인데, 도커허브와 같이 널리 알려진 컨테이너 리파지토리에 저장되서 배포되는 이미지도 보안적으로 문제가 있는 이미지가 많다. 그래서, 가급적이면 벤더들에서 보안적으로 문제가 없도록 관리하는 베이스 이미지를 사용하는 것이 좋다. (구글에서 제공하는 도커 컨테이너 베이스 이미지 https://cloud.google.com/container-registry/docs/managed-base-images)

컨테이너 이미지 스캐닝 툴 (Anchore)

다음 방법으로는 컨테이너 이미지를 구울때마다 보안적인 문제가 없는지, 알려진 보안 취약점을 스캔해서 분석하는 방법인데, 이러한 도구를 vulnerability scaning 툴이라고 한다.

이런 툴중에 Anchore 라는 툴이 있는데, 상업 버전도 있지만 오픈 소스 버전이 있어서 손쉽게 사용이 가능하다. Anchore는 보안 CVE (Common Vulnerabilities and Exposures) 목록을 기반으로 해서 스캔하는데, CVE는 미정부에서 후원하는 National Cybersecurity FFRDC 에 의해서 관리된다.  

이외에도 몇개의 보안 취약점 정보를 몇군데서 가지고 오는데 (https://anchore.freshdesk.com/support/solutions/articles/36000020579-feeds)

재미있는 것은 NPM과 루비젬에 대한 보안 취약점 피드도 가지고 온다는 점이다.

  • Linux Distributions

    • Alpine Linux

    • CentOS

    • Debian

    • Oracle Linux

    • Red Hat Enterprise Linux

    • Ubuntu

  • Software Package Repositories

    • RubyGems.org

    • NPMJS.org

  • NIST National Vulnerability Database (NVD)


간단하게 들여다 보기

설치 및 사용 방법은 공식 홈페이지를 참고하면 되고, https://anchore.freshdesk.com/support/solutions/articles/36000020729-install-with-docker-compose 에는 docker-compose를 이용해서 간단하게 설치 및 사용하는 방법이 나와 있다. 아래는 공식 홈페이지의 내용을 참고해서 설명한 내용이다.


  1. 컨테이너 이미지를 anchore에 추가한다.
    다음 예제 명령은 dokcer
    % docker-compose exec anchore-engine anchore-cli --u admin --p foobar image add docker.io/library/debian:7

  2. anchore는 등록된 이미지를 자동으로 스캔한다.
    다음 명령어를 이용하면 스캔 상태를 모니터링 할 수 있다.
    % docker-compose exec anchore-engine anchore-cli --u admin --p foobar image get docker.io/library/debian:7 | grep 'Analysis Status'

  3. 스캔이 끝나면, 발견된 보안 취약점과 상세 내용을 확인할 수 있다.
    % docker-compose exec anchore-engine anchore-cli --u admin --p foobar image vuln docker.io/library/debian:7 all
    Vulnerability ID        Package                 Severity Fix Vulnerability URL                                                 
    CVE-2005-2541           tar-1.26+dfsg-0.1+deb7u1                 Negligible None https://security-tracker.debian.org/tracker/CVE-2005-2541         
    CVE-2007-5686           login-1:4.1.5.1-1+deb7u1                 Negligible None https://security-tracker.debian.org/tracker/CVE-2007-5686         
    CVE-2007-5686           passwd-1:4.1.5.1-1+deb7u1                Negligible None https://security-tracker.debian.org/tracker/CVE-2007-5686         
    CVE-2007-6755           libssl1.0.0-1.0.1t-1+deb7u4              Negligible None https://security-tracker.debian.org/tracker/CVE-2007-6755         
    ...
    ...
    ...

여기서 CVE-XXX 식으로 나오는 것이 보안 취약점이며, 자세한 내용은 뒤에 나오는 링크에서 확인이 가능하다.

CI/CD 파이프라인 통합

실제 개발/운영 환경에서 사용하려면, 커맨드 라인 뿐만 아니라 CI/CD 빌드 파이프라인에 통합을 해야 하는데, Anchore는 젠킨스 플러그인으로 제공되서 빌드 파이프라인에 쉽게 통합이 된다.

아래와 같은 개념으로 젠킨스에서 이미지를 빌드한 후에, 쿠버네티스나 기타 컨테이너 솔루션에 배포전에 보안 취약점을 스캔할 수 있다.


<그림. Anchore를 젠킨스 빌드 파이프라인에 추가 한 그림 >

출처 : https://anchore.com/opensource/


아래는 Anchore를 젠킨스 플러그인으로 설치한후에, 컨테이너 보안 취약점을 스캔한 결과를 출력해준 화면이다.




국내에는 아직 많이 알려지지 않은것 같은데, 젠킨스와 통합해서 사용한다면 꽤나 좋은 효과를 볼 수 있지 않을까 한다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

minikube에서 서비스 테스트 하기

미니쿠베를 로컬환경에 설치하고 쿠버네티스 서비스를 로드 밸런서 타입으로 배포하면, External IP할당이 되지 않는다. 그래서 아래 그림과 같이 External-IP가 계속 <pending>으로 보이게 된다.


NAME         TYPE  CLUSTER-IP  EXTERNAL-IP PORT(S)          AGE

kubernetes   ClusterIP  10.96.0.1  <none> 443/TCP          7d2h

my-service   LoadBalancer  10.105.173.146  <pending> 8080:31203/TCP   4m10s


그러면 미니쿠베에서 서비스를 테스트하려면 어떻게 해야 할까? 미니쿠베는 서비스를 테스트하기 위해서 service라는 명령을 제공한다. 아래 그림과 같이 minikube service {쿠버네티스 서비스명} 을 입력하면, 로컬 브라우져에서 해당 서비스를 접속할 수 있도록 해준다.


% minikube service my-service

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

요즘 쿠버네티스를 로컬환경에서 이것 저것 테스트하고 있는데, 실행 방법은 다음과 같다.  

환경은 minikube를 인스톨하였다. (0.33.1 버전)


sudo -E minikube start --vm-driver=none --extra-config=kubelet.resolv-conf=/run/systemd/resolve/resolv.conf


쿠버네티스를 우분투에서 실행할때, 별도의 Virtual Machine 없이 실행이 가능하다. VM 없이 실행하려면 --vm-driver=none 옵션을 줘야 한다. 이때, Local DNS Pod 가 기동될때 문제가 생기는데, 이를 해결하기 위해서 --extra-config=kubelet.resolv-conf=/run/systemd/resolve/resolv.conf 옵션을 주면된다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

StatefulSet을 이용하여 상태가 유지되는 Pod 관리하기

조대협 (http://bcho.tistory.com)

ReplicaSet으로 Stateful Pod 관리하기

앞에서 쿠버네티스의 Pod를 관리하기 위한 여러가지 컨트롤러 (Replica Set, ReplicationController,Job 등)에 대해서 알아보았다.

이런 컨트롤러들은 상태가 유지되지 않는 애플리케이션(Stateless application)을 관리하기 위해 사용된다. Pod가 수시로 리스타트되어도 되고, Pod 내의 디스크 내용이 리스타트되어 유실되는 경우라도 문제가 없는 워크로드 형태이다. 웹서버나 웹애플리케이션 서버 (WAS)등이 그에 해당한다. 그러나 RDBMS나 NoSQL과 같은  분산 데이타 베이스등과 같이 디스크에 데이타가 유지 되어야 하는 상태가 유지되는 애플리케이션 (Stateful application)은 기존의 컨트롤러로 지원하기가 어렵다.

ReplicaSet (이하 RS)를 이용하여 데이타 베이스 Pod를 관리하게 되면 여러가지 문제가 발생한다.

Pod의 이름

RS 등, Stateless Pod를 관리하는 컨트롤러에 의해서 관리되는 Pod들의 이름은 아래 그림과 같이 그 이름이 불 규칙적으로 지정된다.



마스터/슬레이브 구조를 가지는 데이타 베이스등에서 마스터 서버의 이름을 특정 이름으로 지정할 수 가 없다.

Pod의 기동 순서

RS에 의해서 관리되는 Pod들은 기동이 될때 병렬로 동시에 기동이 된다. 그러나 데이타베이스의 경우에는 마스터 노드가 기동된 다음에, 슬레이브 노드가 순차적으로 기동되어야 하는 순차성을 가지고 있는 경우가 있다.

볼륨 마운트

Pod에 볼륨을 마운트 하려면, Pod는 PersistentVolume (이하 PV)를 PersistentVolumeClaim(이하 PVC)로 연결해서 정의해야 한다.

RS등의 컨트롤러를 사용해서 Pod를 정의하게 되면, Pod 템플릿에 의해서 PVC와 PV를 정의하게 되기 때문에, 여러개의 Pod들에 대해서 아래 그림과 같이 하나의 PVC와 PV만 정의가 된다. RS의 Pod 템플릿에 의해 정의된 Pod들은 하나의 PVC와 연결을 시도 하는데, 맨 처음 생성된 Pod가 이 PVC와 PV에 연결이 되기 때문에 뒤에 생성되는 Pod들은 PVC를 얻지 못해서 디스크를 사용할 수 없게 된다.   


아래 YAML 파일은 위의 내용을 테스트 하기 위해서 작성한 파일이다.


apiVersion: v1

kind: PersistentVolumeClaim

metadata:

name: helloweb-disk

spec:

accessModes:

  - ReadWriteOnce

resources:

  requests:

    storage: 30Gi

---

apiVersion: v1

kind: ReplicationController

metadata:

name: nginx

spec:

replicas: 3

selector:

  app: nginx

template:

  metadata:

    name: nginx

    labels:

      app: nginx

  spec:

    containers:

    - name: nginx

      image: nginx:1.7.9

      volumeMounts:

      - name: nginx-data

        mountPath: /data/redis

      ports:

      - containerPort: 8090

    volumes:

    - name: nginx-data

      persistentVolumeClaim:

        claimName: helloweb-disk



nginx Pod를 RC를 이용하여 3개를 만들도록 하고, nginx-data 라는 볼륨을 helloweb-disk라는 PVC를 이용해서 마운트 하는 YAML 설정이다. 이 설정을 실행해보면 아래 그림과 같이 nginx-2784n Pod 하나만 생성된다.




%kubectl describe pod nginx-6w9xf

명령을 이용해서 다른 Pod가 기동되지 않는 이유를 조회해보면 다음과 같은 결과를 얻을 수 있다.



내용중에 중요한 내용을 보면 다음과 같다.


“Multi-Attach error for volume "pvc-d930bfcb-2ec0-11e9-8d43-42010a920009" Volume is already used by pod(s) nginx-2784n”


앞에서 설명한 대로, 볼륨(PV)이 다른 Pod (nginx-2784n)에 의해 이미 사용되고 있기 때문에 볼륨을 사용할 수 없고, 이로 인해서, Pod 생성이 되지 않고 있는 상황이다.


RS로 이를 해결 하려면 아래 그림과 같이 Pod 마다 각각 RS을 정의하고, Pod마다 각기 다른 PVC와 PV를 바인딩하도록 설정해야 한다.



그러나 이렇게 Pod 마다 별도로 RS와 PVC,PV를 정의하는 것은 편의성 면에서 쉽지 않다.

StatefulSet

그래서 상태를 유지하는 데이타베이스와 같은 애플리케이션을 관리하기 위한 컨트롤러가 StatefulSet 컨트롤러이다. (StatefulSet은 쿠버네티스 1.9 버전 부터 정식 적용 되었다. )

StatefulSet은 앞에서 설명한 RS등의 Stateless 애플리케이션이 관리하는 컨트롤러로 할 수 없는 기능들을 제공한다. 대표적인 기능들은 다음과 같다.

Pod 이름에 대한 규칙성 부여

StatefulSet에 의해서 생성되는 Pod들의 이름은 규칙성을 띈다. 생성된 Pod들은 {Pod 이름}-{순번} 식으로 이름이 정해진다. 예를 들어 Pod 이름을 mysql 이라고 정의했으면, 이 StatefulSet에 의해 생성되는 Pod 명들은 mysql-0, mysql-1,mysql-2 … 가 된다.

배포시 순차적인 기동과 업데이트

또한 StatefulSet에 의해서 Pod가 생성될때, 동시에 모든 Pod를 생성하지 않고, 0,1,2,.. 순서대로 하나씩 Pod를 생성한다. 이러한 순차기동은 데이타베이스에서 마스터 노드가 기동된 후에, 슬레이브 노드가 기동되어야 하는 조건등에 유용하게 사용될 수 있다.

개별 Pod에 대한 디스크 볼륨 관리

RS 기반의 디스크 볼륨 관리의 문제는 하나의 컨트롤러로 여러개의 Pod에 대한 디스크를 각각 지정해서 관리할 수 없는 문제가 있었는데, StatefulSet의 경우 PVC (Persistent Volume Claim)을 템플릿 형태로 정의하여, Pod 마다 각각 PVC와 PV를 생성하여 관리할 수 있도록 한다.


그럼 StatefulSet 예제를 보자


apiVersion: apps/v1

kind: StatefulSet

metadata:

name: nginx

spec:

selector:

  matchLabels:

    app: nginx

serviceName: "nginx"

replicas: 3

template:

  metadata:

    labels:

      app: nginx

  spec:

    terminationGracePeriodSeconds: 10

    containers:

    - name: nginx

      image: k8s.gcr.io/nginx-slim:0.8

      ports:

      - containerPort: 80

        name: web

      volumeMounts:

      - name: www

        mountPath: /usr/share/nginx/html

volumeClaimTemplates:

- metadata:

    name: www

  spec:

    accessModes: [ "ReadWriteOnce" ]

    storageClassName: "standard"

    resources:

      requests:

        storage: 1Gi


RS나 RC와 크게 다른 부분은 없다. 차이점은 PVC를 volumeClaimTemplate에서 지정해서 Pod마다 PVC와 PV를 생성하도록 하는 부분이다. 위의 볼드처리한 부분


이 스크립트를 실행하면 아래와 같이 Pod가 배포 된다.



pod의 이름은 nginx-0,1,2,... 식으로 순차적으로 이름이 부여되고 부팅 순서도 0번 pod가 기동되고 나면 1번이 기동되고 다음 2번이 기동되는 식으로 순차적으로 기동된다.


template에 의해서 PVC가 생성되는데, 아래는 생성된 PVC 목록이다. 이름은 {StatefulSet}-{Pod명} 식으로 PVC가 생성이 된것을 확인할 수 있다.


그리고 마지막으로 아래는 PVC에 의해서 생성된 PV(디스크 볼륨)이다.

기동 순서의 조작

위의 예제에 보는것과 같이, StatefulSet은 Pod를 생성할때 순차적으로 기동되고, 삭제할때도 순차적으로 (2→ 1 → 0 생성과 역순으로) 삭제한다. 그런데 만약 그런 요건이 필요 없이 전체가 같이 기동되도 된다면 .spec.podManagementPolicy 를 통해서 설정할 수 있다.

.spec.podManagementPolicy 는 디폴트로 OrderedReady 설정이 되어 있고, Pod가 순차적으로 기동되도록 설정이 되어 있고, 병렬로 동시에 모든 Pod를 기동하고자 하면  Parallel 을 사용하면 된다.

아래는 위의 예제에서 podManagementPolicy를 Parallel로 바꾼 예제이다.

apiVersion: apps/v1

kind: StatefulSet

metadata:

name: nginx

spec:

selector:

  matchLabels:

    app: nginx

serviceName: "nginx"

podManagementPolicy: Parallel

replicas: 3

template:

  metadata:

    labels:

      app: nginx

  spec:

    terminationGracePeriodSeconds: 10

    containers:

:

Pod Scale out and in

지금까지 StatefulSet에 대한 개념과 간단한 사용방법에 대해서 알아보았다. 그러면, StatefulSet에 의해 관리되는 Pod가 장애로 인하거나 스케일링 (In/out)으로 인해서 Pod의 수가 늘거나 줄면 그에 연결되는 디스크 볼륨은 어떻게 될까?


예를 들어 아래 그림과 같이 Pod-1,2,3 이 기동되고 있고, 이 Pod들은 StatefulSet에 의해서 관리되고 있다고 가정하자. Pod들은 각각 디스크 볼륨 PV-1,2,3 을 마운트해서 사용하고 있다고 하자.



이때, Pod-3가 스케일인이 되서, 없어지게 되면, Pod는 없어지지면, 디스크 볼륨을 관리하기 위한 PVC-3는 유지 된다. 이는 Pod 가 비정상적으로 종료되었을때 디스크 볼륨의 내용을 유실 없이 유지할 수 있게 해주고, 오토 스케일링이나 메뉴얼로 Pod를 삭제했을때도 동일하게 디스크 볼륨의 내용을 유지하도록 해준다.



그러면 없앴던 Pod가 다시 생성되면 어떻게 될까? Pod가 다시 생성되면, Pod 순서에 맞는 번호로 다시 생성이 되고, 그 번호에 맞는 PVC 볼륨이 그대로 붙게 되서, 다시 Pod 가 생성되어도 기존의 디스크 볼륨을 그대로 유지할 수 있다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 준티 2019.06.30 09:24 신고  댓글주소  수정/삭제  댓글쓰기

    잘 읽었습니다. 감사합니다!


PodDisruptionBudget

조대협 (http://bcho.tistory.com)


Pod의 개수는 컨트롤러가 붙어 있을 경우에는 컨트롤러 스펙에 정의된 replica 수 만큼을 항상 유지 하도록 되어 있다. Pod의 수가 replica의 수를 유지 하지 못하고 줄어드는 경우가 있는데, 애플리케이션이 크래쉬 나거나, VM이 다운되는 등의 예상하지 못한 사고로 인한 경우가 있고, 또는 시스템 관리자가 업그레이드등의 이슈로 노드를 인위적으로 다운 시키는 것과 같이 예상 가능한 상황이 있다.

예상 가능한 상황에서 Pod가 없어지는 것을 Voluntary disruptions 라고 하고, 커널 패닉이나 VM 크래쉬같은 예기치 못한 상황에서 Pod가 없어지는 것을 Involuntary disruptions 라고 한다.


노드를 인위적으로 줄일 경우(Voluntary disruptions) 그 노드에 Pod가 여러개 돌고 있을 경우 순간적으로 Pod의 수가 줄어들 수 있다. 예를 들어 웹 서버 Pod가 노드1에 5개, 노드 2에 8개가 돌고 있을 때 노드1을 다운 시키면 순간적으로 Pod의 총 수가 8개가 된다. replica수에 의해서 복귀는 되겠지만, 성능을 유지하기 위해서 일정 수의 Pod 수를 유지해야 하거나, NoSQL 처럼 데이타 저장에 대한 안정성을 확보하기 위해서 쿼럼값만큼  최소 Pod를 유지해야 하는 경우 , 이런 노드 다운은 문제가 될 수 있다.


그래서 인위적인 노드 다운등과 같이 volutary disruption 상황에도 항상 최소한의 Pod수를 유지하도록 해주는 것이 PodDistruptionBudget (이하 PDB)이라는 기능이다.  PDB를 설정하면 관리자가 노드 업그레이드를 위해서 노드를 다운 시키거나 또는 오토스케일러에 의해서 노드가 다운될 경우, Pod수를 일정 수 를 유지하지 못하면 노드 다운이나 오토스케일러에 의한 스케일 다운등을 막고, Pod 수를 일정 수준으로 유지할 수 있을때 다시 그 동작을 하도록 한다.


예를 들어 nginx-pod가 2개가 유지되도록 PDB를 설정했다고 가정하자


node-1

node-2

node-3

nginx-pod-1

nginx-pod-2

nginx-pod-3


위와 같은 상태에서 시스템 관리자가 node-1에 대한 패치를 위해서 node-1을 다운 시키려고 node draining 명령을 실행했다고 하자. 그럼 node-1에 있는 nginx-pod-1은 다른 node로 옮겨진다.(이를 eviction이라고 한다. ) 구체적으로는 nginx-pod-1 은 terminate 되고, node-2나 node-3에 새로운 pod가 생기게 된다. 여기서는 node-2에 nginx-pod-4라는 이름으로 새로운 pod가 생성된다고 가정하자


node-1 (draining)

node-2

node-3

nginx-pod-1(terminating)

nginx-pod-2

nginx-pod-4 (starting)

nginx-pod-3


이때 마음급한 관리자가 nginx-pod-4가 기동되는 것을 기다리지 않고, node-2를 패치하기 위해서 node-3 를 다운 시키려고 draining 명령을 내렸다고 하자.

이 경우 nginx-pod-4가 아직 기동중인 상태이기 때문에, 현재 사용 가능한 pod는 nginx-pod-2 와 nginx-pod-3 2개 뿐인데, node-3를 다운 시키면 nginx-pod-2 하나 밖에 남지 않기 때문에, PDB에서 정의한 pod의 개수 2개를 충족하지 못하기 때문에, node-3에 대한 다운 작업은 블록 된다.


PDB는 YAML 파일을 이용해서 리소스로 정의될 수 있다.

PDB에서 정의 하는 방법은 minAvailable 정의할 경우 앞의 예제에서 설명한 바와 같이 최소한 유지해야 하는 Pod의 수를 정의할 수 있고 또는 maxUnavailable 을 정의해서 최대로 허용할 수 있는 Unhealthy한 Pod 의 수를 정의할 수 있다. 이 두 값은 Pod의 숫자로 정의할 수 도 있고 또는 %를 사용할 수 있다.


예를 들어

  • minAvailable 가 5이면, 최소한 5개의 Pod 는 항상 정상 상태로 유지하도록 한다.

  • minAvailable 가 10%이면,  Pod의 replica 수를 기준으로 10%의 Pod 는 항상 정상 상태로 유지하도록 한다.

  • maxUnavailable 가 1이면, 최대 한개의 Pod만 비정상 상태를 허용하도록 한다.


그러면 이렇게 정의한 값들은 어떻게 Pod에 적용할까, PDB를 만든후에, 이 PDB안에 label selector를 정의해서 PDB를 적용할 Pod를 선택할 수 있다.


완성된 예제는 다음과 같다.

apiVersion: policy/v1beta1
kind: PodDisruptionBudget
metadata:
 name: zk-pdb
spec:
 minAvailable: 2
 selector:
   matchLabels:
     app: zookeeper


< 출처 : https://kubernetes.io/docs/tasks/run-application/configure-pdb/#specifying-a-poddisruptionbudget >


이 예제는 app: zookeeper라고 되어 있는 Pod 들을 최소 2개는 항상 유지할 수 있도록 하는 설정이다.

지금까지 간단하게 PDB에 대한 개념을 설명하였다. 일반적인 Stateless 애플리케이션에는 크게 문제가 없겠지만, 일정 성능 이상을 요하는 애플리케이션이나, NoSQL과 같은 일정 수의 쿼럼 유지가 필요한 애플리케이션등의 경우에는 PDB를 정의하는 것이 좋다.


참고로 현재 PDB는 베타 상태이고 정식 릴리즈 상태는 아니기 때문에, 운영 환경에는 아직 사용하지 않는 것이 좋다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

PodPreset


애플리케이션을 배포하는데 있어서, 애플리케이션 실행 파일과과 설정 정보를 분리해서 환경에 따라 설정 정보만 변경해서 애플리케이션을 재배포하는 방법이 가장 효율적이다.  


쿠버네티스에서는 ConfigMap을 이용하여 이러한 정보를 환경 변수로 넘기거나, 또는 파일이나 디렉토리로 마운트하는 방법을 제공해왔다. ConfigMap을 이용하는 경우 ConfigMap에서 여러 정보를 꺼내오도록 해야 한다. 또한 반복적으로 유사한 애플리케이션을 배포하고자 할때, 디스크가 필요한 경우에는 볼륨과 그 마운트에 대한 정의를 해야하기 때문에, 애플리케이션을 배포하는 Pod의 설정 파일이 다소 복잡해질 수 있다.


그래서 새롭게 소개되는 개념이 PodPreset 이다.

PodPreset에는 환경 변수 이외에도, 볼륨과 볼륨 마운트 정보를 정의할 수 있다.

이렇게 정의된 PodPreset은 label selector를 통해서 적용할 Pod를 지정할 수 있다.

label selector에 의해서 지정된 Pod는 Pod가 생성될때, PodPreset에 지정된 정보가 추가되서 Pod가 생성된다.


이해가 잘 안될 수 있는데, 예제를 살펴보는게 가장 빠르다. 예제는 쿠버네티스 공식 홈페이지에 있는 예제를 사용하였다.

https://kubernetes.io/docs/tasks/inject-data-application/podpreset/#simple-pod-spec-example



아래는 PodPreset  예제이다.

apiVersion: settings.k8s.io/v1alpha1
kind: PodPreset
metadata:
 name: allow-database
spec:
 selector:
   matchLabels:
     role: frontend
 env:
   - name: DB_PORT
     value: "6379"
 volumeMounts:
   - mountPath: /cache
     name: cache-volume
 volumes:
   - name: cache-volume
     emptyDir: {}


env 부분에서 DB_PORT 환경 변수에  데이타 베이스 접속 포트를 정해주었고, /cache 디록토리에 emptyDir을 마운트 하도록 하는 설정이다. 이 PodPreset을 role: frontend 인 Pod 들에 적용하도록 하였다.


아래는 Pod 정의 예제이다.


apiVersion: v1
kind: Pod
metadata:
 name: website
 labels:
   app: website
   role: frontend
spec:
 containers:
   - name: website
     image: nginx
     ports:
       - containerPort: 80

내용을 보면 알겠지만, 간단하게 nginx 웹서버를 실행하는 Pod 설정 파일이다.

그런데 이 Pod를 배포하게 되면 앞에 PodPreset에서 Selector로 role:frontend 를 적용하도록 하였기 때문에, 이 Pod에는 앞에서 정의한 PodPreset이 적용되서 실제 배포 모양은 아래와 같이 된다.


apiVersion: v1
kind: Pod
metadata:
 name: website
 labels:
   app: website
   role: frontend
 annotations:
   podpreset.admission.kubernetes.io/podpreset-allow-database: "resource version"
spec:
 containers:
   - name: website
     image: nginx
     volumeMounts:
       - mountPath: /cache
         name: cache-volume
     ports:
       - containerPort: 80
     env:
       - name: DB_PORT
         value: "6379"
 volumes:
   - name: cache-volume
     emptyDir: {}


원래 Pod 설정 파일에 있는 내용에 추가해서 env, volumeMount, volumes 부분이 PodPreset에 정의된데로 추가 적용된것을 확인할 수 있다.


Configmap이 이미 있는데, 이런게 왜 필요할까 하고 잠깐 살펴봤는데, 편하다. 쿠버네티스에서 디스크 볼륨 설정이 그다지 간략하다고 말하기가 어렵고 그래서, 개발자 입장에서 애플리케이션을 간략하게 배포하고 싶은데, 디스크에 대한 설정을 해야 하는데, 이 부분을 모두 PodPreset에서 관리자가 해줄 수 있는 구조로 빠질 수 있다. 아직 알파단계 API이기 때문에 환경 정보와 볼륨 관련 정보만 지정하는데, 정식 버전에서는 좀 더 많은 설정 정보를 제공했으면 한다.



본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

Kong API gateway

아키텍쳐 | 2019. 2. 7. 23:11 | Posted by 조대협

Kong API gateway 간단 리뷰 노트 


요즘 MSA가 다시 올라오기 시작하고 있고, Kubernetes Istio 조합으로 좋은 아키텍쳐를 많이 그려낼 수 있게 되기 시작해서, API 게이트웨이를 다시 살펴보고 있는데, 시장에 API 게이트 웨이들은 대부분 인수가 된 상황이고, Kubernetes에서는 Ambassador가 올라오고 있는데, Istio 통합이나 Kubernetes 통합 기능은 강력하지만, 아직 신생 제품이라 기능이 그리 많이 않고, 보통 오픈 소스 대세는 Kong을 많이 이야기 하길래 쭈욱 살펴봤는데, 일단 CLI 명령이 거의 없고 제대로 이해한것이 맞다면, REST CALL로 설정을 잡아야 하고, Kubenetes 에 Ingress로 올라갈 수 도 있고, Istio 통합도 로드맵에는 있지만 아직은 완성전 단계..  설치 스크립트를 따라가 보니 Cassandra나 Postgres를 뒷단에 놓는것이 아마도 로그 수집 용도인것 같은데, 가볍게 사용하기에는 설정이 너무 무겁다. 

GUI 도구로는 지원 도구인 Konga가 있기는 하지만 UI를 봤을때 너무 단순하기도 하다. 

지켜보기는 하지만, 아직은 살짝 기다려야 하는 상태가 아닌가 싶다. 


참고 https://medium.com/@tselentispanagis/managing-microservices-and-apis-with-kong-and-konga-7d14568bb59d


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. Skull Island 2019.07.17 18:17  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다

Ksonnet

조대협(http://bcho.tistory.com)


쿠버네티스의 리소스 배포는 YAML 스크립트를 기반으로 한다.

하나의 마이크로 서비스를 배포하기 위해서는 최소한 Service, Deployment 두개 이상의 배포 스크립트를 작성해야 하고, 만약에 디스크를 사용한다면 Persistent Volume (aka PV)와 Persistent Volume Claim (PVC)등 추가로 여러 파일을 작성해서 배포해야 한다.

그런데 이러한 배포 작업을 보면, 사실 비슷한 성격의 마이크로 서비스간에는 중복 되는 부분이 많다.


예를 들어 간단한 웹서비스 (node.js나 springboot)를 배포할때는 Service의 타입을 지정하고, Deployment에 의해서 관리되는 Pod의 수, 그리고 컨테이너 이미지 정도만 지정하면 기본적인 배포는 가능하다. 여기에 필요하다면, 요구되는 리소스 (CPU, 메모리) 정도만 지정하면 된다.

그러나 이를 위해서는 Service와 Deployment를 정의한 긴 YAML 파일을 작성해야 한다.

그렇다면 이러한 반복작업을 단순화하고, 환경(운영/개발, 또는 미국/유럽과 같은 다른 리전등)에도 반복적으로 재활용할 수 있는 방법은 없을까?


이런 목적에서 여러가지 솔루션이 소개되는데, 그중에 널리 사용되고 있는 것은 패키지 매니저인 Helm이 있다. Helm은 node.js 의 npm이나, 리눅스의 apt 유틸리티와 유사하게, 패키지 형태로 쿠버네티스 리소스를 배포할 수 있도록 해준다. 이외에도 여러가지 솔루션이 있는데, 오늘 이야기하는 솔루션은 ksonnet이라는 오픈소스로, 템플릿엔진 형태의 특징을 가지고 있는 솔루션이다.


Ksonnet

ksonnet은 JSON 기반의 템플릿 엔진인 jsonnet (https://jsonnet.org/)을 기반으로 한다. 기본적으로 템플릿 엔진의 특성을 가지고 있는데, 특이점이 인프라를 지원하는데 있어서 일종의 프로그래밍 언어적인 컨셉을 지원한다.

그러면 기본적인 개념을 이해 해보도록 하자


Prototype, Parameter & Component



먼저 Prototype 이라는 개념을 이해 해야하는데, prototype의 템플릿의 개념으로, 객체 지향형 프로그램에서 클래스의 개념으로 생각하면 된다. 이 prototype을 이용해서 component를 만들어 낸다. component는 객체 지향형에서 객체의 개념으로 생각하면 된다. 이렇게 만들어진 컴포넌트에 속성(변수)를 설정하면 되는데, Deployment 이름이나, 컨테이너 이미지등 실배포에서 애플리케이션 서비스별로 필요한 구체적인 값을 입력한다. 이 값을 입력하는 방법은 parameter 라는 리소스로 정의해서, 필요한 값을 전달한다.

이렇게 값이 채워진 component는 각 서비스 배포에 필요한 쿠버네티스 리소스들을 정의한 쿠버네티스 manifest 파일로 생성된다. (참고 : YAML이 아니라 JSON으로 생성되는데, 쿠버네티스의 manifest는 YAML뿐만 아니라 JSON으로도 설정이 가능하다. )


prototype에서 component를 생성하는 방식은 앞에서도 설명하였듯이, 하나의 prototype에서 parameter만 바꿔서 생성하면 다른 서비스에 대한 component를 반복적으로 생성할 수 있다. 아래 개념 그림은 하나의 prototype에서 도커 이미지와 서비스 타입만 변경해서 Guestbook과 BookStore 서비스에 대해서 각각의 컴포넌트를 생성하는 개념이다.



Environment

이렇게 생성된 component는 쿠버네티스에 적용해서, 실제 쿠버네티스 리소스를 만드는데, ksonnet은 동시에 여러개의 쿠버네티스 환경을 지원할 수 있다. 이를 ksonnet 안에서는 environment 라는 리소스로 정의하는데, 여러개의 쿠버네티스 클러스터를 하나의 ksonnet 프로젝트 안에 등록해놓고, 생성된 component를 등록된 쿠버네티스 환경중 하나에 배포한다. ksonnet의 environment는 쿠버네티스 클러스터에 대한 정보 (예를 들어 마스터 서버의 IP)를 가지고 있다.



Part

그런데 Prototype도 유사점이 있다. 예를 들어 redis 배포를 예를 들었을때, 어떤 redis 배포는 외장 디스크 (PV)를 붙여서 배포할 수 도 있고, 아니면 내장 디스크 (PV 없이) 배포할 수 있다. 비슷한 타입의 prototype의 반복적인 작업을 막기 위해서 prototype은 재사용이 가능한 자원으로 조합해서 정의 될 수 있다. 이를 ksonnet에서는 part라고 정의한다.




위의 그림을 보면 우측에 두가지 프로토 타입이 정의된것을 볼 수 있다. 하나는 PVC(Persistent Volume Claim)이 있는 것이고 하나는 없는 것이데, 이외에 차이가 없고 Service나 Deployment는 정의는 동일 하기 때문에 이를 part로 정의한 후에, 이 part를 조합하여 다른 prototype을 구성할 수 있다.

Application

하나의 애플리케이션은 여러개의 마이크로 서비스로 구성이 된다. 그래서 하나의 애플리케이션을 배포하기 위해서는 여러개의 Prototype과 component들이 필요하게 되는데, 하나의 쿠버네티스 클러스터에는 열개의 애플리케이션이 설치될 수 있기 때문에, 이 애플케이션 단위로, 리소스를 묶을 수 있는 바운더리 개념이 필요하다. ksonnet에서는 이 개념을 application이라는 단위로 정의해서, 이 애플리케이션 안에, 여러개의 prototype, part, component, environment를 묶을 수 있게 한다.

그래서 이 애플리케이션 단위로 설치도 가능하고 반대로 애플리케이션 단위로 설치된 애플리케이션을 삭제할 수 있다.

Package

Part와 Prototype은 다른 애플리케이션에서도 재 사용될 수 있다. 예를 들어 redis 설치 스크립트나, nginx 설치 스크립트 역시 어느 애플리케이션에서나 재 사용될 수 있고, 머신러닝 패키지인 kueflow와 같은 복잡한 애플리케이션의 경우에는 이 prototype과 part들만 있으면 손쉽게 설치될 수 있다. 이렇게 각 모듈들을 재사용 가능하도록 묶어놓은 것을 ksonnet에서는 package라고 한다. Package 안에는 일반적으로 아래와 같이 재 사용이 가능한 part와 prototype들이 들어가 있다.


Registry

그러면 이 패키지들 어디에 저장하고 어떻게 내 애플리케이션에 반영할까? ksonnet에서는 npm이나 docker repository처럼 이러한 패키지들을 저장할 수 있는 레파지토리 개념을 제공하는데, 이것이 바로 registry다.

Github repository나, 파일 시스템 Helm repository를 registry로 사용할 수 있다.


작성한 package는 이 registry에 저장할 수 있으며, 저장된 package들은 package install 명령어를 이용해서 설치한다.



Hello ksonnet

ksonnet의 개념을 이해했으면, 실제 테스트를 통해서 구체적은 사용법을 알아보도록 하자. 테스트를 위해서는 쿠버네티스 클러스터가 설치되어 있어야 하는데, 쿠버네티스 환경이 없는 경우에는 온라인에서 테스트할 수 있는 환경이 https://ksonnet.io/tour/welcome/ 에 있으니,이를 활용하는 것도 하나의 방법이다.

여기서 사용한 예제는 ksonnet 공식 홈페이지에 나와 있는 Guestbook 예제이다. https://ksonnet.io/docs/tutorial/guestbook/ 함축적으로 설명을 해놓은 간단한 예제라서 디테일한 내용을 이해하기는 어렵지만 대략적인 컨셉과 흐름을 이해하는데는 큰 무리가 없다.

애플리케이션 생성

먼저 애플리케이션을 생성한다. ks init 명령을 이용하면 애플리케이션 디렉토리가 생성되고, 필요한 초기 리소스들이 생성된다. 일종의 boiler plate와 같은 개념으로 보면 된다.

%ks init guest book

생성이 되면 아래와 같은 디렉토리 구조로 생성이 된다.

components에는 컴포넌트 파일과 parameter 등이 저장되는데, 정의된 컴포넌트가 없기 때문에, 컴포넌트 관련 파일은 없이 비어 있다.

Environment 디렉토리는 쿠버네티스 환경에 대한 정보가 들어있는데, 기본 환경값은 defaults라는 디렉토리에 저장된다.



Environment 생성

현재 환경을 cloud 라는 이름으로 등록해보자. 환경 등록은 ks add env라는 명령을 사용하면 된다. 등록해야하는 대상 쿠버네티스 클러스터가 많을 경우, 다른 환경을 추가로 등록하면 된다.

%ks add env cloud

Component 생성

다음으로 컴포넌트를 생성하는데, 등록된 prototype을 기반을 component를 생성해야 하는데, ks application을 생성하면 디폴트로 등록되어 있는 prototype이 있다. 등록되어 있는 prototype 목록은 ks prototype list 명령으로 조회가 가능하다.


%ks prototype list




여기서 사용할 prototype은 deployed-service를 사용하는데, image와 type 만을 지정한다.

이 prototype은 웹서비스를 위해서 쿠버네티스 리소스인 Service 와 Deployment를 생성해준다.

deployed-service prototype을 이용해서, 컴포넌트를 생성하기 위해서는 ks generate deployed-service {컴포넌트명} 으로 생성하고, 필요한 parameter는 --{parameter name}={parameter value) 식으로 지정한다.


% ks generate deployed-service guestbook-ui \

 --image gcr.io/heptio-images/ks-guestbook-demo:0.1 \

 --type ClusterIP


Component 생성이 끝나면 ~/components/ 디렉토리에 컴포넌트 파일이 생성된다.


생성된 컴포넌트 파일의 일부를 보면 다음과 같다.  컴포넌트들이 생성되고, parameter 처리되어 있는 부분은 아래 마크한바와 같이 parameter value를 각각 항목마다 지정되어 있다.


~/components/guestbook-ui.libsonnet

중략

:

 {

   "apiVersion": "apps/v1beta2",

   "kind": "Deployment",

   "metadata": {

     "name": params.name

   },

   "spec": {

     "replicas": params.replicas,

     "selector": {

       "matchLabels": {

         "app": params.name

       },

     },

     "template": {

       "metadata": {

         "labels": {

           "app": params.name

         }

       },

       "spec": {

         "containers": [

           {

             "image": params.image,

             "name": params.name,

             "ports": [

               {

                 "containerPort": params.containerPort

               }

             ]

           }

         ]

       }



     "name": params.name

   },

   "spec": {

     "ports": [

       {

         "port": params.servicePort,

         "targetPort": params.containerPort

       }

     ],

     "selector": {

       "app": params.name

     },

     "type": params.type

   }

 },


이하 생략

그리고 parameter로 지정한 내용들은 별도의 param.libsonnet 이라는 파일에 정의된다. 아래 그림과 같이 image명이나 type들이 앞에서 ks generate 단계에서 지정한 내용으로 정의된것을 확인할 수 있다. 다른 값들은 prototype에서 정의된 디폴트 값으로 채워진다.


~/components/param.libsonnet

local env = std.extVar("__ksonnet/environments");

local params = std.extVar("__ksonnet/params").components["guestbook-ui"];

[

중략

 components: {

   // Component-level parameters, defined initially from 'ks prototype use ...'

   // Each object below should correspond to a component in the components/ directory

   "guestbook-ui": {

     containerPort: 80,

     image: "gcr.io/heptio-images/ks-guestbook-demo:0.1",

     name: "guestbook-ui",

     replicas: 1,

     servicePort: 80,

     type: "ClusterIP",

   },

중략

생성된 Manifest 확인하기

생성된 컴포넌트가 각 쿠버네티스 환경에 어떻게 적용되는지를 살펴보기 위해서는  ks show {환경이름} 명령을 실행하면, 쿠버네티스에 적용된 YAML manifest 파일을 먼저확인해볼 수 있다.

%ks show cloud


다음은 “cloud” 라는 환경에 반영할 수 있는 YAML manifest 파일이다.

아래 그림은 YAML 파일중에서 Deployment 파트인데,  아래 그림고 같이 name이나 label등 구체적인 설정 값들이 앞에서 지정한 parameter 값으로 채워진것을 확인할 수 있다.

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 labels:

   ksonnet.io/component: guestbook-ui

 name: guestbook-ui

spec:

 replicas: 1

 selector:

   matchLabels:

     app: guestbook-ui

 template:

   metadata:

     labels:

       app: guestbook-ui

   spec:

     containers:

     - image: gcr.io/heptio-images/ks-guestbook-demo:0.1

       name: guestbook-ui

       ports:

       - containerPort: 80



중략

Manifest 적용

이렇게 컴포넌트가 정의되었으면, 대상 쿠버네티스를 선택해서 ks apply 명령으로 적용 하면 된다. 아래 명령어는 “cloud” 라는 환경에 생성된 컴포넌트들을 적용하는 명령이다.

%ks apply cloud

적용된 서비스 확인 및 테스트

쿠버네티스에 적용된 리소스를 확인해보자. kubectl get svc와 kubectl get pod를 실행해보면 아래와 같이 Service와 Pod들이 생성된것을 확인할 수 있다.

%kubectl get svc




%kubectl get pod




생성된 서비스를 테스트해보자. 서비스를 ClusterIP로 생성하였기 때문에, 외부 IP를 통해서 접속은 불가능하고, kubectl proxy를 이용해서 프록시를 세팅해서 접속해야 한다. 아래는 kubectl proxy 명령어를 이용해서 proxy를 생성하고 접속을 하는 예제이다.

%kubectl proxy > /dev/null &

%KC_PROXY_PID=$!

%SERVICE_PREFIX=http://localhost:8001/api/v1

%GUESTBOOK_URL=$SERVICE_PREFIX/namespaces/default/services/guestbook-ui/proxy/


%open $GUESTBOOK_URL



프록시를 통해서 접속한 웹사이트에 대한 결과이다.


간단하게 컨셉과 예제를 통해서 실제 사용 방법을 확인해보았다.

ksonnet은 쿠버네티스의 다른 YAML 파일 배포 시스템과 다르게 prototype,component,application,environment 등과 같이 잘 추상화된 개념을 가지고 있으며, 특히 배포를 위해서 쿠버네티스에 무엇인가를 설치할 필요가 없이 클라이언트에 ks 툴만 설치하면 되기 때문에, 쿠버네티스 설치에 대한 의존성이 없이 깔끔하게 설치 및 운영이 가능하다.

또한 파일 형태로 모든 리소스를 관리할 수 있기 때문에 gitOps와 같은 형상 관리 시스템을 통해서 리소스를 관리할 수 있기 때문에, 변경 추적이나 코드 재 사용등에 많은 장점을 가질 수 있다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 개발자큐 2019.01.17 08:30 신고  댓글주소  수정/삭제  댓글쓰기

    좋은 포스팅 감사합니다! 그림이랑 예시가 있으니 이해가 쉽게 되네요ㅎㅎ

End2End 머신러닝 플랫폼 Kubeflow 조대협 (http://bcho.tistory.com)

머신러닝 파이프라인

머신러닝에 대한 사람들의 선입견중의 하나는 머신러닝에서 수학의 비중이 높고, 이를 기반으로한 모델 개발이 전체 시스템의 대부분 일 것이라는 착각이다.

그러나 여러 연구와 경험을 참고해보면, 머신러닝 시스템에서 머신러닝 모델이 차지하는 비중은 전체의 5% 에 불과하다.


실제로 모델을 개발해서 시스템에 배포할때 까지는 모델 개발 시간보다 데이타 분석에 소요되는 시간 그리고 개발된 모델을 반복적으로 학습하면서 튜닝하는 시간이 훨씬 더 길다.

머신러닝 파이프라인은 데이타 탐색에서 부터, 모델 개발, 테스트 그리고 모델을 통한 서비스와 같이 훨씬 더 복잡한 과정을 거친다. 이를 머신러닝 End to End 파이프라인이라고 하는데, 자세하게 그 내용을 살펴보면 다음 그림과 같다.




  • Data ingestion : 머신러닝에 필요한 학습 데이타를 외부로 부터 받아서 저장하는 단계

  • Data analytics : 수집된 데이타를 분석하여, 의미를 찾아내고,필요한 피쳐(특징)을 찾아내는 단계로 주로 빅데이타 분석 시스템이 많이 활용된다. EDA (Exploratory Data Analytics) 방법을 많이 사용하는데, 저장된 데이타를 그래프로 시각화해서 각 값간의 관계나 데이타의 분포등을 분석한다.

  • Data Transformation : 수집된 데이타에서 학습에 필요한 데이타만 걸러내고, 학습에 적절하도록 컨버팅 하는 단계. 예를 들어 이미지 데이타의 크기를 정형화하고, 크롭핑 처리를 한후에, 행렬 데이타로 변환하는 과정등이 이에 해당한다.

  • Data Validation : 변환된 데이타가 문제는 없는지 데이타 포맷이나 범위등을 검증하는 단계

  • Data Splitting : 머신러닝 학습을 위해서 데이타를 학습용,테스트용,검증용으로 나눈다.

  • Build a Model : 머신러닝 모델을 만들고 학습하는 단계

  • Model Validation : 만들어진 모델을 검증하는 단계

  • Training at scale : 더 많은 데이타를 더 큰 인프라에서 학습 시켜서 정확도를 높이고, 하이퍼 패러미터 튜닝을 통해서 모델을 튜닝하는 단계로 주로 대규모 클러스터나 GPU 자원등을 활용한다.

  • Roll out : 학습된 모델을 운영환경에 배포하는 단계

  • Serving : 배포된 모델을 통해서 머신러닝 모델을 서비스로 제공하는 형태. 유스케이스에 따라서 배치 형태로 서빙을 하거나 실시간으로 서빙하는 방법이 있다.

  • Monitoring : 머신러닝 모델 서비스를 모니터링 해서 정확도등에 문제가 없는지 지속적으로 관찰하는 단계

  • Logging : 모델에 서비스에 대한 로그 모니터링


이 과정을 데이타의 변동이 있거나 모델을 향상시키고자 하거나 정확도가 떨어지는 경우 첫번째 과정부터 반복을 한다.


위에서 설명한 파이프라인 흐름을 시스템 아키텍쳐로 표현해보면 다음과 같다.




먼저 GPU를 지원하는 인프라 위에 머신러닝 플랫폼이 올라가게 되고, 빅데이타 분석 플랫폼이 같이 사용된다.

머신러닝 플랫폼은 데이타를 분석하는 EDA 단계의 데이타 분석 플랫폼 그리고, 분석된 데이타를 변환 및 검증하고 학습,테스트,검증 데이타로 나누는 Data Processing 시스템이 붙고, 이 데이타를 이용해서, 모델을 개발한후에, 이 모델을 학습 시키기 위한 학습 (Training) 플랫폼이 필요하다. 학습된 모델을 검증하고, 이 검증 결과에 따라서 하이퍼 패러미터를 튜닝한 후에, 이를 운영환경에 배포하여 서비스 한다. 데이타 분석 및 모델 개발 학습 단계는 주로 데이타 사이언티스트에 의해서 이루어지는데, 이러한 엔지니어들이 사용할 개발 환경이 필요한데, 주로 노트북 기반 (예. 파이썬 주피터 노트북)의 환경이 많이 사용된다.

학습이 완료된 모델을 서빙하는 Inference 엔진이 필요하고, 이를 외부 API로 노출하기 위해서 API 키 인증, 오토스케일링, 로깅 및 모니터링을 위한 API Serving 플랫폼이 필요하다.


컴포넌트가 많은 만큼 여기에 사용되는 프레임웍도 많다. 먼저 모델 개발 및 학습을 위해서는 머신러닝 프레임웍이 필요한데, Tensorflow, PyTorch, Sklearn, XGBoost등 목적에 따라서 서로 다른 프레임웍을 사용하게 되며, 완성된 모델을 서빙하는 경우에도 Tensorflow Serving, Uber에서 개발한 Horovod 등 다양한 플랫폼이 있다. 또한 모델을 서빙할때 REST API등으로 외부에 서비스 하려면 보안 요건에 대한 처리가 필요하기 때문에 별도의 API 인증 메커니즘등이 추가되어야 하고, 스케일링을 위한 오토 스케일링 지원 그리고 모델의 배포와 테스트를 위한 배포 프레임웍, A/B 테스트 환경등이 준비되어야 한다.

일부만 이야기한것이지만 실제 운영 환경에서 사용되는 머신러닝 시스템은 훨씬 더 복잡하고 많은 기술을 필요로 한다.

Kubeflow comes in

이러한 복잡성 때문에 머신러닝 플랫폼은 높은 난이도를 가지고 있고, 데이타 분석과 모델 개발에 집중해야 하는 머신러닝 엔지니어 입장에서는 큰 부담이 된다. (배보다 배꼽이 크다)

그래서 이러한 복잡성을 줄이고 머신러닝 엔지니어의 원래 업인 데이타 분석과 머신러닝 모델 개발에만 집중할 수 있도록 플랫폼을 추상화 해놓은 오픈 소스 프레임웍이 Kubeflow이다.

위에서 설명한 머신러닝 파이프라인의 End to End 전체를 커버할 수 있게 하고, 모든 단계의 컴포넌트를 패키지화 해놔서, 어려운 설치 없이 머신러닝 엔지니어는 머신러닝 모델 개발의 각 단계를 손쉽게 할 수 있도록 해준다.


Kuberflow는 Kubernetes(쿠버네티스) + ml flow 를 합한 의미로, 쿠버네티스 플랫폼 위에서 작동한다.

쿠버네티스는 도커 컨테이너 관리 플랫폼으로, 이 컨테이너 기술을 이용하여 머신러닝에 필요한 컴포넌트를 패키징하여 배포한다. 쿠버네티스에 대한 자세한 설명은 링크를 참고하기 바란다.

이로 인해서 가질 수 있는 장점은 다음과 같다.

  • 클라우드나 On-Prem (데이타 센터), 개인 개발 환경에 상관 없이 동일한 머신러닝 플랫폼을 손쉽게 만들 수 있기 때문에 특정 벤더나 플랫폼에 종속되지 않는다.

  • 컨테이너 기술을 이용해서 필요한 경우에만 컨테이너를 생성해서 사용하고, 사용이 끝나면 컨테이너를 삭제하는 방식이기 때문에 자원 활용율이 매우 높다. 특히 쿠버네티스의 경우에는 스케쥴링 기능을 이용해서 비어있는 하드웨어 자원에 컨테이너를 배포해서 (꾸겨넣는 방식으로) 사용하기 때문에 집적률이 매우 높다.

  • 컨테이너로 패키징이 되어있기 때문에 내부 구조를 알필요가 없이 단순하게 컨테이너만 배포하면 된다.

또한 쿠버네티스는 오픈소스 플랫폼이기 때문에 여러 종류의 머신러닝 관련 기술들이 손쉽게 합쳐지고 있다.

Kubeflow 컴포넌트 구성

그러면 간단하게 Kubeflow의 컴포넌트 구성을 살펴보자.

IDE 환경

IDE 개발환경으로는 JupyterLab을 지원한다. JupyterLab은 Jupyter 노트북의 확장 버전으로 코드 콘솔뿐 아니라 파일 브라우져나 시각화창등 확장된 UI를 지원한다.


<출처 : https://jupyterlab.readthedocs.io/en/stable/getting_started/overview.html>


개인적으로 기존 노트북 환경에 비해서 좋은 점은 주피터 노트북을 필요할때 마다 손쉽게 생성이 가능하며, 생성할때 마다 GPU 지원 여부나 텐서플로우 버전등을 손쉽게 선택이 가능하다.


<그림. 노트북 생성시 텐서플로우와 GPU 지원 여부를 선택하는 화면>


또한 아래 그림과 같이 노트북 인스턴스의 하드웨어 스펙 (CPU, Memory, GPU)를 정의할 수 있다.



GPU 드라이버

그리고 쿠버네티스상에서 GPU를 사용할 수 있도록 GPU 드라이버를 미리 패키징 해놓았다. 머신러닝 프레임웍을 사용하면 항상 까다로운 부분이 GPU 드라이버 설정이나 업그레이드인데, 이를 미리 해놓았기 때문에 머신러닝 엔지니어 입장에서 별도의 노력없이 손쉽게 GPU를 사용할 수 있다.

머신러닝 프레임웍

머신러닝 프레임웍으로는 현재 텐서플로우, 파이토치, MxNet등을 지원하는데, 플러그인 컴포넌트 형태이기 때문에 앞으로 더 많은 프레임웍을 지원할 것으로 기대된다.

데이타 프로세싱

데이타 프로세싱에서 데이타 변환 (Transformation)과 데이타 검증 (Validation)은 텐서플로우의 확장팩인 TFX에서 지원하는 TFDV (Tensorflow Data Validation)과 TFT (Tensorflow Transform)을 이용해서 지원한다.

학습 환경

개발된 모델을 학습할때 특히 분산학습의 경우에는 텐서플로우 클러스터와 우버에서 개발된 텐서플로우용 분산 학습 플랫폼인 Hornovod를 지원한다.  

모델 검증

학습된 모델 검증은 데이타 프로세싱과 마친가지로 텐서플로우 확장팩인 TFX의 TFMA (Tensorflow Model Analysis)를 지원한다.

하이퍼 패러미터 튜닝

학습된 모델에 대한 하이퍼 패레미터 튜닝은 katLib라는 컴포넌트를 이용해서 지원한다.

모델 서빙

학습이 완료된 모델은 TFX 패키지의 일부인 Tensorflow Serving 을 사용하거나 모델 서빙 전문 플랫폼인 SeldonIO를 사용한다. SeldonIO는 텐서플로우뿐만 아니라 Sklearn, Spark 모델, H2O 모델, R 모델등 좀 더 다양한 모델을 지원한다.

API 서비스

서비스된 모델에 대한 API 키 인증이나 라우팅등을 위해서 API 게이트 웨이가 필요한데, API 게이트 웨이로 Ambassador라는 오픈 소스를 이용한다. 이를 통해서 API 키등의 인증을 지원하고, 쿠버네티스 위에 네트워크 플랫폼인 ISTIO를 사용하여, API 서비스에 대한 모니터링 및 서비스 라우팅을 지원하는데, 서비스 라우팅 기능은 새 모델을 배포했을때 새모델로 트래픽을 10%만 보내고 기존 모델로 트래픽을 90% 보내서 새모델을 테스트하는 카날리 테스트나 API 통신에 대한 보안등 여러기능을 지원한다. Istio에 대한 자세한 설명은 링크를 참조하기 바란다.

워크플로우

이러한 컴포넌트를 매번 메뉴얼로 실행할 수 는 없고, 워크플로우 흐름에 따라서 자동으로 파이프라인을 관리할 수 있는 기능이 필요한데, 이를 워크플로우 엔진이라고 하고, Kubeflow에서는 argo라는 컨테이너 기반의 워크플로우 엔진을 사용한다. 자세한 내용은 링크 참조.

그런데 argo는 일반적인 워크플로우를 위해서 디자인된 플랫폼으로 머신러닝 파이프라인에 최적화되어 있지 않다. (예를 들어 학습 단계 종료후, 학습 결과/accuracy등을 모니터링 한다던지, Tensorflow Dashboard와 통합된다던지.) 그래서 argo위해 머신러닝 기능을 확장하여 개발중인 오픈소스가 Kubeflow pipeline이 있다. Kubeflow pipeline에 대해서는 나중에 더 자세히 설명하도록 한다.

컴포넌트에 대한 정의

Kubeflow에서 사용되는 거의 모든 컴포넌트에 대해서 설명하였다. 그러면 이런 컴포넌트를 어떻게 쿠버네티스에 배포하고, 어떻게 실행을 할것인가? 매번 쿠버네티스의 설정 파일을 만들어서 하기에는 파일의 수도 많고 반복작업이면서 또한 쿠버네티스에 대한 높은 전문성을 필요로하기 때문에 어렵다.

그래서 이러한 반복작업을 줄여주고, 템플릿화하여 실행하도록 해주는 엔진이 ksonnet 이라는 오픈소스를 사용한다. ksonnet은 jsonnet 템플릿 엔진 기반으로, 위에서 나열한 컴포넌트들을 쿠버네티스에 설치할 수 있도록 해주고, 각 단계별 컴포넌트를 손쉽게 실행할 수 있도록 해준다.


이 솔루션들을 앞에서 설명한 머신러닝 플랫폼 아키텍쳐에 맵핑 시켜보면 다음과 같은 그림이 된다.




Kubeflow는 현재 개발중인 버전으로 이글을 쓰는 현재 0.4 버전이 개발중이다.

컨셉적으로 매우 훌륭하고 0.4 버전인것에 비해서는 매우 완성도가 높지만 1.0 릴리즈 전이기 때문에 다소 변화가 심하기 때문에 버전간 호환이 안될 수 있다. 이점을 염두하고 사용하기 바란다.


Kubeflow를 이해하기 위해서는 먼저 Kubeflow의 컴포넌트를 배포하고 실행하게 해주는 ksonnet에 대한 이해가 먼저 필요하다. 다음 글에서는 이 ksonnet에 대해서 알아보도록 하겠다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. dongjoo 2019.01.09 14:14  댓글주소  수정/삭제  댓글쓰기

    매번 좋은내용 포스팅 해주셔서 감사합니다. 관심이 많이 있는 분야인데, 이제 프레임웍을 통째로 개발하는것보단 얼마나 잘 개발(유지)되고 있는 오픈소스를 잘 조합하고 각자에 맞게 개발해서 더 큰 value를 뽑아낼 수 있느냐가 점점 중요해지는것 같네요.

  2. 초보자 2019.04.10 17:14  댓글주소  수정/삭제  댓글쓰기

    항상 새로운 정보를 여기에서 얻는것 같네요. 매번 좋은 글 올려주셔서 감사합니다.

  3. ksmin 2020.01.09 14:01  댓글주소  수정/삭제  댓글쓰기

    안녕하세요.
    내용 중에 hyperparameter tuning을 katlib라고 하셨는데 katib 아닌가요?
    좋은 글 감사합니다.


컨테이너 기반의 워크플로우 솔루션 argo

조대협 (http://bcho.tistory.com)


argo는 컨테이너 워크플로우 솔루션이다.

컨테이너 기반으로 빅데이타 분석, CI/CD, 머신러닝 파이프라인을 만들때 유용하게 사용할 수 있는 오픈 소스 솔루션으로 개념은 다음과 같다.


워크플로우를 정의하되 워크플로우의 각각의 스텝을 컨테이너로 정의한다.

워크플로우 스펙은 YAML로 정의하면, 실행할때 마다 컨테이너를 생성해서, 작업을 수행하는 개념이다.


기존에 아파치 에어플로우 (https://airflow.apache.org/)등 많은 워크 플로우 솔루션이 있지만, 이러한 솔루션은 컴포넌트가 VM/컨테이너에서 이미 준비되서 돌고 있음을 전제로 하고, 각각의 컴포넌트를 흐름에 따라서 호출하는데 목적이 맞춰서 있다면, argo 의 경우는 워크플로우를 시작하면서 컨테이너를 배포하고, 워크플로우 작업이 끝나면 컨테이너가 종료되기 때문에, 실행할때만 컨테이너를 통해서 컴퓨팅 자원을 점유하기 때문에 자원 활용면에서 장점이 있다고 볼 수 있다.


argo 설치는 쿠버네티스 클러스터가 있는 상태라면 https://argoproj.github.io/docs/argo/demo.html 를 통해서 간단하게 설치가 가능하다. 설치와 사용법은 위의 문서링크를 활용하기 바란다.

HelloWorld

간단한 워크플로우 예제를 살펴보자. 워크 플로우를 실행하기 위해서는 워크플로우 스펙을 yaml 파일로 정의해야 한다. 아래는 helloworld 의 간단한 예제이다.


apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: hello-world-

spec:

 entrypoint: whalesay

 templates:

 - name: whalesay

   container:

     image: docker/whalesay:latest

     command: [cowsay]

     args: ["hello world"]


워크플로우의 이름은 metadata 부분에 generateName에서 워크플로우 JOB의 이름을 정의할 수 있다. 여기서는 hello-world-로 정의했는데, 작업이 생성될때 마다 hello-world-xxx 이라는 이름으로 작업이 생성된다.

Templates 부분에 사용하고자 하는 컨테이너를 정의한다. 위의 예제에서는 docker/whalesay:latest 이미지로 컨테이너를 생성하도록 하였고, 생성후에는 “cosway”라는 명령어를 “hello world” 라는 인자를 줘서 실행하도록 하였다.

Template 부분에는 여러개의 컨테이너를 조합하여, 어떤 순서로 실행할지를 정의한다. 이 예제에서는 하나의 컨테이너만 실행하도록 정의하였다.

다음에 어느 컨테이너 부터 시작하게 할것인지는 sepc 부분에 정의하고, 시작 부분은 entrypoint라는 구문을 이용해서 정의할 수 있다.이 예제에서는 template에 정의한 whalesay라는 컨테이너부터 실행하도록 한다.

이렇게 생성된 워크플로우 스펙은 “argo submit”이라는 CLI 명령을 이용해서 실행한다.


%argo submit --watch {yaml filename}


워크플로우가 실행되면 각 단계별로 쿠버네티스 Pod 가 생성되고, 생성 결과는 argo logs {pod name}으로 확인할 수 있다.


%argo list

명령을 이용하면 argo 워크플로우의 상태를 확인할 수 있다.



위의 그림과 같이 hello-world는 hello-world-smjxq 라는 작업으로 생성되었다.

Pod 명은 이 {argo 작업이름}-xxx 식으로 명명이 된다.

%kubectl get pod

명령으로 확인해보면 아래 그림과 같이 hello-world-smjxq 라는 이름으로 pod가 생성된것을 확인할 수 있다.


이 pod의 실행 결과를 보기 위해서

%argo logs hello-world-smjxq

명령을 실행하면 된다.


위의 그림과 같이 고래 그림을 결과로 출력한것을 확인할 수 있다.

ArgoUI

워크플로우의 목록과 실행결과는 CLI뿐 아니라 웹 기반의 GUI에서도 확인이 가능하다.

argo ui는 argo라는 이름의 deployment에 생성이 되어 있는데, clusterIP (쿠버네티스 내부 IP)로 생성이 되어 있기 때문에 외부에서 접근이 불가능하다. 포트포워딩 기능을 이용해서 argo deployment의 8001 포트를 로컬 PC로 포워딩해서 접속할 수 있다.


% kubectl -n argo port-forward deployment/argo-ui 8001:8001


다음에 http://localhost:8001을 이용해서 접속해보면 다음과 같이 현재 등록되어 있는 워크플로우 목록을 확인할 수 있다.




이 목록에서 아까 수행한 hello-world-xxx 워크플로우를 확인해보자. 아래 그림과 같이 워크플로우의 구조를 보여준다.



hello-world-xxx 노드를 클릭하면 각 노드의 상세 내용을 볼 수 있다.


그림에서 Summary > Logs 부분을 선택하면 아래 그림과 같이 각 단계별로 실행한 결과 로그를 볼 수 있다.


연속된 작업의 실행

앞에서 간단한 설치 및 사용법에 대해서 알아봤는데, 앞에서 살펴본 예제는 하나의 태스크로 된 워크플로우이다. 워크플로우는 좀더 복잡하게 여러개의 태스크를 순차적으로 실행하거나 또는 병렬로 실행이 된다.


예제 원본 https://argoproj.github.io/docs/argo/examples/README.html


다음 워크플로우 정의를 보자

apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: steps-

spec:

 entrypoint: hello-hello-hello


 # This spec contains two templates: hello-hello-hello and whalesay

 templates:

 - name: hello-hello-hello

   # Instead of just running a container

   # This template has a sequence of steps

   steps:

   - - name: hello1            #hello1 is run before the following steps

       template: whalesay

       arguments:

         parameters:

         - name: message

           value: "hello1"

   - - name: hello2a           #double dash => run after previous step

       template: whalesay

       arguments:

         parameters:

         - name: message

           value: "hello2a"

     - name: hello2b           #single dash => run in parallel with previous step

       template: whalesay

       arguments:

         parameters:

         - name: message

           value: "hello2b"


 # This is the same template as from the previous example

 - name: whalesay

   inputs:

     parameters:

     - name: message

   container:

     image: docker/whalesay

     command: [cowsay]

     args: ["{{inputs.parameters.message}}"]


구조를 살펴보면 다음과 같다.



  • Metadata 부분에 generateName으로 이 워크플로우의 이름을 정의했다. 워크플로우 작업이 실행될때마다 steps-xxx  라는 이름으로 생성이 된다.

  • 워크 플로우는 templates 부분에 spec 부분에 정의하는데, hello1 작업을 수행한 후에, hello2a,hello2b 를 동시에 실행한다. 동시 실행인지 순차 실행인지는 steps의 인덴트(탭으로 띄워쓰기 한부분)을 확인하면 되는데, hello1은 - -name: hello1으로 정의 하였고 다음 단계는 - -name: hello2a와, - name:hello2b로 지정하였다. 잘 보면, hello2b는 “-”가 두개가 아니고 한개로 되어 있고 hello2a와 같은 띄어 쓰기로 되어 있는 것을 볼 수 있다.

  • 마지막으로 이 워크플로우에서 사용되는 컨테이너 이미지를 정의하면 된다.


실행시에 --watch 옵션을 주면, 각 단계별 실행 상태와, 워크플로우 그래프의 구조를 볼 수 있다.


%argo submit --watch helloworld-seq.yaml


위의 그림을 보면 step-h44qf라는 이름으로 작업이 수행되는데, hello1이 먼저 실행되고 다음에 hello2a,hello2b가 동시에 실행된것을 확인할 수 있다.


실행후에 UI에서 실행 내역을 확인해보면 다음과 같이 hello1이 먼저 실행 된 후에,  hello2a,hello2b가 병렬로 동시에 실행된것을 확인할 수 있다.



DAG를 이용한 워크플로우 정의

Yaml 파일 형식이 워크플로우의 실행 순서를 정의할 경우 명시성이 떨어져서 가독성 측면에서 읽기에 불편할 수 있는데, DAG(Directed acyclic graph)를 이용하면 조금 더 명시적으로 워크플로우 정의가 가능하다.



apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: dag-diamond-

spec:

 entrypoint: diamond

 templates:

 - name: echo

   inputs:

     parameters:

     - name: message

   container:

     image: alpine:3.7

     command: [echo, "{{inputs.parameters.message}}"]

 - name: diamond

   dag:

     tasks:

     - name: A

       template: echo

       arguments:

         parameters: [{name: message, value: A}]

     - name: B

       dependencies: [A]

       template: echo

       arguments:

         parameters: [{name: message, value: B}]

     - name: C

       dependencies: [A]

       template: echo

       arguments:

         parameters: [{name: message, value: C}]

     - name: D

       dependencies: [B, C]

       template: echo

       arguments:

         parameters: [{name: message, value: D}]


entrypoint는 diamond dag를 실행하도록 한다.

dag 정의 부분을 보면 맨 앞에 name: A인 task를 실행하도록 하고, 다음 B,C는 A에 의존성을 가지도록 한다.D는 B,C의 의존성을 가지게 해서 실행 순서는 A→ B,C → D형태가 된다.

다음과 같은 순서로 실행이 된다.



입력/출력값 전달

argo의 개념과 워크플로우의 개념을 이해했으면, 워크플로우에서 태스크간의 데이타를 어떻게 전달하는지 살펴보도록 하자.

입력값의 전달

argo에서 변수를 입력값으로 사용하는 방법은 간단하다. 먼저 변수를 정의한 다음에, 정의된 변수를 입력이나 출력으로 사용할지 워크플로우의 태스크에서 정의한후, 그 변수를 사용하면 된다.


apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: hello-world-parameters-

spec:

 # invoke the whalesay template with

 # "hello world" as the argument

 # to the message parameter

 entrypoint: whalesay

 arguments:

   parameters:

   - name: message

     value: hello world


 templates:

 - name: whalesay

   inputs:

     parameters:

     - name: message       #parameter declaration

   container:

     # run cowsay with that message input parameter as args

     image: docker/whalesay

     command: [cowsay]

     args: ["{{inputs.parameters.message}}"]


위의 코드를 살펴보면 먼저 spec.arguments 부분에서 message라는 변수를 선언 하였고, 그 값은 “hello world”로 초기화를 했다.

그리고 워크플로우의 whalesay 태스크에서 message 변수를 input 변수로 사용하도록 선언하였다. 그 후에, args에서 input.parameters.message를 참조하여 message변수의 값을 도커 컨테이너의 실행 변수로 넘기도록 하였다.


만약의 변수의 값을 CLI에서 바꾸고자 한다면 다음과 같이 argo submit시에 -p 옵션을 주면 된다. argo submit {workflow yaml file name} -p {parameter name}={value}

아래는 message=”hello terry”로 바꿔서 실행한 예이다.

ex) argo submit argument.yaml -p message=”hello terry”

출력값 사용

아래 코드를 보자.

아래 코드는 whalesay 컨테이너의 결과를 print-message 컨테이너로 넘기는 코드이다.


apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: output-parameter-

spec:

 entrypoint: output-parameter

 templates:

 - name: output-parameter

   steps:

   - - name: generate-parameter

       template: whalesay

   - - name: consume-parameter

       template: print-message

       arguments:

         parameters:

         # Pass the hello-param output from the generate-parameter step as the message input to print-message

         - name: message

           value: "{{steps.generate-parameter.outputs.parameters.hello-param}}"


 - name: whalesay

   container:

     image: docker/whalesay:latest

     command: [sh, -c]

     args: ["echo -n hello world > /tmp/hello_world.txt"]  #generate the content of hello_world.txt

   outputs:

     parameters:

     - name: hello-param       #name of output parameter

       valueFrom:

         path: /tmp/hello_world.txt    #set the value of hello-param to the contents of this hello-world.txt


 - name: print-message

   inputs:

     parameters:

     - name: message

   container:

     image: docker/whalesay:latest

     command: [cowsay]

     args: ["{{inputs.parameters.message}}"]


구조를 보면 다음과 같은데


먼제 whalesay 정의 부분을 보면 outputs.parameters에 hello-param이라는 이름으로 output 변수를 정의하였고, output의 내용은 /tmp/hello_world.txt 파일 내용으로 채워진다.


다음 print-message 컨테이너 정의 부분을 보면 input param으로 message라는 변수를 정의하였다.

steps를 보면, print-message를 실행할때, message 변수의 값을 {{steps.generate-parameter.outputs.parameters.hello-param}} 로 정의하여, print-message의 이전 스탭인 generate-parameter의 output param중에 hello-param이라는 변수의 값으로 채우는 것을 볼 수 있다.


이 흐름을 그림으로 도식화 해보면 다음과 같다.


Whalesay 컨테이너에서 /tmp/hello_world.txt 파일 내용을 hello-param이라는 output param으로 전달하고, print-message 컨테이너는 입력값으로 message라는 param을 받는데, 이 값을 앞단계의 hello-param의 값을 받도록 한것이다.

Artifact

워크플로우 태스크에 대한 입/출력값을 parameter로 전달할 수 도 있지만, CI/CD 빌드 파이프라인에서는 소스코드, 빌드 바이너리가 될 수 도 있고, 빅데이타 파이프라인에서는 데이타 파일과 같이 큰 사이즈의 파일이나 데이타가 될 수 있다. 이 경우 parameter 를 이용해서 넘기기에는 부담이 되는데, 이런 요구 사항을 위해서 제공되는 것이 artifact라는 기능이다. 태스크의 결과값을 로컬 스토리지가 아니라, AWS S3나 GCP GCS와 같은 외부 스토리지에 쓸 수 있게 하고, 반대로 태스크에 대한 입력 값을 외부 스토리지에서 읽어올 수 있게 하는 기능이다.


예를 들어 텐서플로우로 학습을 시키는 파이프라인이 있을때, 학습된 모델을 S3나 GCS에 저장하도록 하는 등의 작업을 할 수 있다.


아래 예제를 보면 앞에서 소개한 generate-artifact → consume-artifact 워크플로우에서 parameter로 값을 넘기는 방식이 artifact 방식으로 바뀐것을 확인할 수 있다. 단순하게 parameter 로 선언한 부분을 artifact로만 변경해주었다.


apiVersion: argoproj.io/v1alpha1

kind: Workflow

metadata:

 generateName: artifact-passing-

spec:

 entrypoint: artifact-example

 templates:

 - name: artifact-example

   steps:

   - - name: generate-artifact

       template: whalesay

   - - name: consume-artifact

       template: print-message

       arguments:

         artifacts:

         # bind message to the hello-art artifact

         # generated by the generate-artifact step

         - name: message

           from: "{{steps.generate-artifact.outputs.artifacts.hello-art}}"


 - name: whalesay

   container:

     image: docker/whalesay:latest

     command: [sh, -c]

     args: ["cowsay hello world | tee /tmp/hello_world.txt"]

   outputs:

     artifacts:

     # generate hello-art artifact from /tmp/hello_world.txt

     # artifacts can be directories as well as files

     - name: hello-art

       path: /tmp/hello_world.txt


 - name: print-message

   inputs:

     artifacts:

     # unpack the message input artifact

     # and put it at /tmp/message

     - name: message

       path: /tmp/message

   container:

     image: alpine:latest

     command: [sh, -c]

     args: ["cat /tmp/message"]


기타

간단하게 기본 개념만 설명했지만, 이외에도 여러가지 기능을 이용하여 좀 더 복잡한 워크플로우를 구현할 수 있다. 예를 들어서 Condition 기능을 이용해서, 조건에 따라서 워크플로우를 분기하거나 조건에 따라 재귀호출을 하는 Recursive 호출, 호출중에 조건에 따라 워크플로우를 종료할 수 있는 기능등을 이용할 수 있다.

이외에도 컨테이너 배포시 사이드카(http://bcho.tistory.com/1256) 를 삽입하여 컨테이너 배포시 로그 수집이나 기타 기능등을 동시에 수행하도록 할 수 있다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

블로그 600만 돌파

사는 이야기 | 2018. 12. 10. 17:03 | Posted by 조대협

2018년12월 10일 600만 돌파

2018년 5월 10일 500만 돌파

2017년 7월17일 405만 돌파

2016년10월20일300만 돌파

2015년12월16일200만 돌파



'사는 이야기' 카테고리의 다른 글

개발자 코딩 노트북 구입기 (DELL Inspiron 7580) 우분투  (8) 2019.03.10
블로그 600만 돌파  (1) 2018.12.10
블로그 500만 돌파  (0) 2018.05.10
지난 1년 회고  (0) 2017.09.20
블로그 400만 돌파  (2) 2017.07.17
2016년 업무 종료....  (3) 2016.12.29
본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 돌팽이. 2018.12.10 17:12 신고  댓글주소  수정/삭제  댓글쓰기

    축하합니다 ^_^

Deep learning VM

아키텍쳐 /머신러닝 | 2018. 12. 5. 05:36 | Posted by 조대협


클라우드에서 pre-built되서 제공되는 VM 이미지

GPU 드라이버, Tensorflow, Skitlearn,Pytorch들도 다 들어가 있고, 노트북이나 텐서보드도 들어가 있음 SSH Shell forwarding을 이용해서 쉽게 접속 가능함


https://cloud.google.com/deep-learning-vm/docs/concepts-images


gcloud compute ssh {VM name} -- -L 8888:localhost:8888 -L 6006:localhost:6006 -L 8080:localhost:8080

'아키텍쳐  > 머신러닝' 카테고리의 다른 글

피쳐 크로싱 (Feature crossing)  (1) 2019.05.21
Deep learning VM  (2) 2018.12.05
본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 2018.12.10 18:02  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. 손현술 2018.12.10 18:04  댓글주소  수정/삭제  댓글쓰기

    관리자의 승인을 기다리고 있는 댓글입니다

Istio #4 - 설치 및 BookInfo 예제

조대협 (http://bcho.tistory.com)

Istio 설치

그러면 직접 Istio 를 설치해보자, 설치 환경은 구글 클라우드의 쿠버네티스 환경을 사용한다. (쿠버네티스는 오픈소스이고, 대부분의 클라우드에서 지원하기 때문에 설치 방법은 크게 다르지 않다.)

쿠버네티스 클러스터 생성

콘솔에서 아래 그림과 같이 istio 라는 이름으로 쿠버네티스 클러스터를 생성한다. 테스트용이기 때문에, 한존에 클러스터를 생성하고, 전체 노드는 3개 각 노드는 4 CPU/15G 메모리로 생성하였다.



다음 작업은 구글 클라우드 콘솔에서 Cloud Shell내에서 진행한다.

커맨드 라인에서 작업을 할것이기 때문에, gCloud SDK를 설치(https://cloud.google.com/sdk/gcloud/) 한후에,

%gcloud auth login

gcloud 명령어에 사용자 로그인을 한다.


그리고 작업을 편리하게 하기 위해서 아래와 같이 환경 변수를 설정한다. 쿠버네티스 클러스터를 생성한 리전과 존을 환경 변수에 아래와 같이 설정한다. 예제에서는 asia-southeast1 리전에 asia-southeast1-c 존에 생성하였다. 그리고 마지막으로 생성한 쿠버네티스 이름을 환경 변수로 설정한다. 예제에서 생성한 클러스터명은 istio이다.

export GCP_REGION=asia-southeast1
export GCP_ZONE=asia-southeast1-c
export GCP_PROJECT_ID=$(gcloud info --format='value(config.project)')
export K8S_CLUSTER_NAME=istio

다음 kubectl 명령어를 사용하기 위해서, 아래과 같이 gcloud 명령어를 이용하여 Credential을 세팅한다

% gcloud container clusters get-credentials $K8S_CLUSTER_NAME \
   --zone $GCP_ZONE \
   --project $GCP_PROJECT_ID

Credential 설정이 제대로 되었는지

% kubectl get pod -n kube-system

명령어를 실행하여, 쿠버네티스 시스템 관련 Pod 목록이 잘 나오는지 확인한다.

Istio 설치

쿠버네티스 클러스터가 준비되었으면, 이제 Istio를 설치한다.

Helm 설치

Istio는 Helm 패키지 매니져를 통해서 설치 한다.

% curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get > get_helm.sh
% chmod 700 get_helm.sh
% ./get_helm.sh

Istio 다운로드

Istio 를 다운로드 받는다. 아래는 1.0.4 버전을 다운 받는 스크립트이다.

% cd ~

% curl -L https://git.io/getLatestIstio | sh -

% cd istio-1.0.4

% export PATH=$PWD/bin:$PATH

Helm 초기화

Istio를 설치하기 위해서 Helm용 서비스 어카운트를 생성하고, Helm을 초기화 한다.

% kubectl create -f install/kubernetes/helm/helm-service-account.yaml

% helm init --service-account tiller

Istio 설치

다음 명령어를 이용하여 Istio를 설치한다. 설치시 모니터링을 위해서 모니터링 도구인 kiali,servicegraph 그리고 grafana 설치 옵션을 설정하여 아래와 같이 추가 설치 한다.

% helm install install/kubernetes/helm/istio \

--name istio \

--namespace istio-system \

--set tracing.enabled=true \

--set global.mtls.enabled=true \

--set grafana.enabled=true \

--set kiali.enabled=true \

--set servicegraph.enabled=true


설치가 제대로 되었는지 kubectl get pod명령을 이용하여, istio 네임스페이스의 Pod 목록을 확인해보자

% kubectl get pod -n istio-system




BookInfo 샘플 애플리케이션 설치

Istio 설치가 끝났으면, 사용법을 알아보기 위해서 간단한 예제 애플리케이션을 설치해보자, Istio에는 BookInfo (https://istio.io/docs/examples/bookinfo/)  라는 샘플 애플리케이션이 있다.

BookInfo 애플리케이션의 구조

아래 그림과 같이 productpage 서비스 안에, 책의 상세 정보를 보여주는 details 서비스와 책에 대한 리뷰를 보여주는 reviews 서비스로 구성이 되어 있다.  


시스템의 구조는 아래와 같은데, 파이썬으로 개발된 productpage 서비스가, 자바로 개발된 review 서비스과 루비로 개발된 details 서비스를 호출하는 구조이며, review 서비스는 v1~v3 버전까지 배포가 되어 있다. Review 서비스 v2~v3는 책의 평가 (별점)를 보여주는  Rating 서비스를 호출하는 구조이다



< 그림 Book Info 마이크로 서비스 구조 >

출처 : https://istio.io/docs/examples/bookinfo/

BookInfo 서비스 설치

Istio의 sidecar injection 활성화

Bookinfo 서비스를 설치하기 전에, Istio의 sidecar injection 기능을 활성화 시켜야 한다.

앞에서도 설명하였듯이 Istio는 Pod에 envoy 를 sidecar 패턴으로 삽입하여, 트래픽을 컨트롤 하는 구조이 다. Istio는 이 sidecar를 Pod 생성시 자동으로 주입 (inject)하는 기능이 있는데, 이 기능을 활성화 하기 위해서는 쿠버네티스의 해당 네임스페이스에 istio-injection=enabled 라는 라벨을 추가해야 한다.

다음 명령어를 이용해서 default 네임 스페이스에 istio-injection=enabled 라벨을 추가 한다.

% kubectl label namespace default istio-injection=enabled


라벨이 추가되었으면

% kubectl get ns --show-labels

를 이용하여 라벨이 제대로 적용이 되었는지 확인한다.


Bookinfo 애플리케이션 배포

Bookinfo 애플리케이션의 쿠버네티스 배포 스크립트는 samples/bookinfo 디렉토리에 들어있다. 아래 명령어를 실행해서 Bookinfo 앺ㄹ리케이션을 배포하자.

% kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml


배포를 완료한 후 kubectl get pod 명령어를 실행해보면 다음과 같이 productpage, detail,rating 서비스가 배포되고, reviews 서비스는 v1~v3까지 배포된것을 확인할 수 있다.



Kubectl get svc 를 이용해서 배포되어 있는 서비스를 확인하자



Prodcutpcage,rating,reviews,details 서비스가 배포되어 있는데, 모두 ClusterIP 타입으로 배포가 되어 있기 때문에 외부에서는 접근이 불가능하다.


Istio gateway 설정

이 서비스를 외부로 노출 시키는데, 쿠버네티스의 Ingress나 Service는 사용하지 않고, Istio의 Gateway를 이용한다.

Istio의 Gateway는 쿠버네티스의 커스텀 리소스 타입으로, Istio로 들어오는 트래픽을 받아주는 엔드포인트 역할을 한다. 여러 방법으로 구현할 수 있으나, Istio에서는 디폴트로 배포되는 Gateway는 Pod 형식으로 배포되어 Load Balancer 타입의 서비스로 서비스 된다.


먼저 Istio Gateway를 등록한후에, Gateway를 통해 서비스할 호스트를 Virtual Service로 등록한다.


아래는 bookinfo에 대한 Gateway를 등록하는 Yaml 파일이다.


apiVersion: networking.istio.io/v1alpha3

kind: Gateway

metadata:

 name: bookinfo-gateway

spec:

 selector:

   istio: ingressgateway # use istio default controller

 servers:

 - port:

     number: 80

     name: http

     protocol: HTTP

   hosts:

   - "*"


selector를 이용해서 gateway 타입을 istio에서 디폴트로 제공하는 Gateway를 사용하였다. 그리고, HTTP프로토콜을 80 포트에서 받도록 하였다.

다음에는 이 Gateway를 통해서 트래픽을 받을 서비스를 Virtual Service로 등록해야 하는데, 그 구조는 다음과 같다.


apiVersion: networking.istio.io/v1alpha3

kind: VirtualService

metadata:

 name: bookinfo

spec:

 hosts:

 - "*"

 gateways:

 - bookinfo-gateway

 http:

 - match:

   - uri:

       exact: /productpage

   - uri:

       exact: /login

   - uri:

       exact: /logout

   - uri:

       prefix: /api/v1/products

   route:

   - destination:

       host: productpage

       port:

         number: 9080


spec에서 gateways 부분에 앞에서 정의한 bookinfo-gateway를 사용하도록 한다. 이렇게 하면 앞에서 만든 Gateway로 들어오는 트래픽은 이 Virtual Servivce로 들어와서 서비스 디는데, 여기서 라우팅 룰을 정의 한다 라우팅룰은 URL에 때해서 어느 서비스로 라우팅할 지를 정하는데 /productpage,/login,/lougout,/api/v1/products URL은 productpage:9080 으로 포워딩해서 서비스를 제공한다.


Gateway와 Virtual service 배포에 앞서서, Istio에 미리 설치되어 있는 gateway를 살펴보면, Istio default gateway는 pod로 배포되어 있는데, istio=ingressgateway 라는 라벨이 적용되어 있다. 확인을 위해서 kubectl get 명령을 이용해서 확인해보면 다음과 같다.

%kubectl get pod -n istio-system -l istio=ingressgateway



이 pod들은 istio-ingressgateway라는 이름으로 istio-system 네임스페이스에 배포되어 있다. kubectl get svc로 확인해보면 다음과 같다.

%kubectl get svc istio-ingressgateway -n istio-system --show-labels



그러면 bookinfo를 istio gateway에 등록해서 외부로 서비스를 제공해보자

% istioctl create -f samples/bookinfo/networking/bookinfo-gateway.yaml


게이트 웨이 배포가 끝나면, 앞에서 조회한 Istio gateway service의 IP (여기서는 35.197.159.13)에 접속해서 확인해보자

브라우져를 열고 http://35.197.159.13/productpage 로 접속해보면 아래와 같이 정상적으로 서비스에 접속할 수 있다.



모니터링 툴

서비스 설치가 끝났으면 간단한 테스트와 함께 모니터링 툴을 이용하여 서비스를 살펴보자

Istio를 설치하면 Prometheus, Grafana, Kiali,Jaeger 등의 모니터링 도구가 기본적으로 인스톨 되어 있다. 각각의 도구를 이용해서 지표들을 모니터링 해보자

Grafana를 이용한 서비스별 지표 모니터링

Grafana를 이용해서는 각 서비스들의 지표를 상세하게 모니터링할 수 있다.

먼저 아래 스크립트를 사용해서 간단하게 부하를 주자. 아래 스크립트는 curl 명령을 반복적으로 호출하여 http://35.197.159.13/productpage 페이지를 불러서 부하를 주는 스크립이다.


for i in {1..100}; do

curl -o /dev/null -s -w "%{http_code}" http://35.197.159.13/productpage

done


다음 Grafana 웹 콘솔에 접근해야 하는데, Grafana는 외부 서비스로 노출이 안되도록 설정이 되어 있기 때문에 kubectl을 이용해서 Grafana 콘솔에 트래픽을 포워딩 하도록 하자. Grafana는 3000번 포트에서 돌고 있기 때문에, localhost:3000 → Grafana Pod의 3000 번 포트로 트래픽을 포워딩 하도록 설정하자


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=grafana -o jsonpath='{.items[0].metadata.name}') 3000:3000 &


다음 localhost:3000 번으로 접속해보면 다음과 같은 화면을 볼 수 있다.

각 서비스 productpage,review,rating,detail 페이지의 응답시간과 OPS (Operation Per Sec : 초당 처리량)을 볼 수 있다.




각 서비스를 눌러보면 다음과 같이 서비스별로 상세한 내용을 볼 수 있다. 응답 시간이나 처리량에 대한 트렌드나, Request의 사이즈등 다양한 정보를 볼 수 있다.



Jaeger를 이용한 분산 트렌젝션 모니터링

다음은 Jaeger 를 이용해 개별 분산 트렌젝션에 대해서 각 구간별 응답 시간을 모니터링 할 수 있다.

Istio는 각 서비스별로 소요 시간을 수집하는데, 이를 Jaeger 오픈소스를 쓰면 손쉽게 모니터링이 가능하다.

마찬가지로 Jaeger 역시 외부 서비스로 노출이 되지 않았기 때문에, kubectl 명령을 이용해서 로컬 PC에서 jaeger pod로 포트를 포워딩하도록 한다. Jaerger는 16686 포트에서 돌고 있기 localhost:16686 → Jaeger pod:16686으로 포워딩한다.


kubectl port-forward -n istio-system $(kubectl get pod -n istio-system -l app=jaeger -o jsonpath='{.items[0].metadata.name}') 16686:16686 &


Jaeger UI에 접속해서, 아래는 productpage의 호출 기록을 보는 화면이다. 화면 상단에는 각 호출별로 응답시간 분포가 나오고 아래는 개별 트렉젝션에 대한 히스토리가 나온다.



그중 하나를 선택해보면 다음과 같은 그림을 볼 수 있다.



호출이 istio-ingressgateway로 들어와서 Productpage를 호출하였다.

productpage는 순차적으로 productpage → detail 서비스를 호출하였고, 다음 productpage→ reviews → ratings 서비스를 호출한것을 볼 수 있고, 많은 시간이 reviews 호출에 소요된것을 확인할 수 있다.


Servicegraph를 이용한 서비스 토폴로지 모니터링

마이크로 서비스는 서비스간의 호출 관계가 복잡해서, 각 서비스의 관계를 시각화 해주는 툴이 있으면 유용한데, 대표적인 도구로는 service graph라는 툴과 kiali 라는 툴이 있다. BookInfo 예제를 위한 Istio 설정에는 servicegraph가 디폴트로 설치되어 있다.


마찬가지로 외부 서비스로 노출 되서 서비스 되지 않고 클러스터 주소의 8088 포트를 통해서 서비스 되고 있기 때문에, 아래와 같이 kubectl 명령을 이용해서 localhost:8088 → service graph pod의 8088포트로 포워딩하도록 한다.


kubectl -n istio-system port-forward $(kubectl -n istio-system get pod -l app=servicegraph -o jsonpath='{.items[0].metadata.name}') 8088:8088 &


그 후에, 웹 브루우져에서 http://localhost:8088/dotviz 를 접속해보면 서비스들의 관계를 볼 수 있다.



다음 글에서는 예제를 통해서 Istio에서 네트워크 경로 설정하는 부분에 대해서 더 자세히 알아보도록 하겠다.


본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 2018.12.05 14:07  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

  2. cjshms@gmail.com 2018.12.05 14:08  댓글주소  수정/삭제  댓글쓰기

    항상 큰 도움이 되십니다. 화이팅!!!!!

ISTIO


조대협 (http://bcho.tistory.com)

Envoy를 이용해서 서비스 매쉬를 구현하기 위해서는 Envoy로 구성된 데이타 플레인을 컨트롤할 솔루션이 필요하다. Envoy를 데이타 플레인으로 사용하고 이를 컨트롤 해주는 오픈 소스 솔루션이 Istio 이다. (http://istio.io)

아키텍쳐

먼저 Istio의 구조를 보자


<그림, Istio 아키텍쳐 >

출처 : https://istio.io/docs/concepts/what-is-istio/

데이타 플레인

데이타 플레인은 envoy를 서비스 옆에 붙여서 사이드카 형식으로 배포를 해서, 서비스로 들어오고 나가는 트래픽을 envoy를 통해서 통제하게 된다.

envoy는 서비스에서 서비스로 호출할때 상대편 서비스의 IP 주소를 알아야 하는데, 이를 서비스 디스커버리 (Service discovery : 참고 http://bcho.tistory.com/1252?category=431297 )

그러면 envoy는 서비스들의 IP 주소 (엔드포인트)를 어떻게 알 수 있을까? 서비스들에 대한 엔드포인트 정보는 컨트롤 플레인의 파일럿(Pilot)이라는 컴포넌트에 저장되어 있고, envoy는 이 데이타를 참고하여 엔드포인트를 알 수 있다.

컨트롤 플레인

컨트롤 플레인은 데이타 플레인에 배포된 envoy를 컨트롤 하는 부분으로, 파일럿 (Pilot), 믹서 (Mixer), 시타델(Citadel) 3개의 모듈로 구성이 되어 있다.

파일럿 (Pilot)

파일럿은 envoy에 대한 설정 관리를 하는 역할을 한다.

먼저 앞에서 언급했듯이 서비스들의 엔드포인트(EndPoint)들의 주소를 얻을 수 있는 서비스 디스커버리 기능을 제공한다.

Istio에 유용한 기능중의 하나가 트래픽의 경로를 컨트롤 하는 기능인데, 서비스에서 서비스로 호출하는 경로를 컨트롤 할 수 있다. 이외도 서비스의 안정성을 제공하기 위해서 서비스간에 호출이 발생할때 재시도(retry), 장애 전파를 막기 위한 써킷 브레이커 (Circuit breaker), Timeout 등의 기능을 제공한다.

믹서(Mixer)

믹서가 하는 일은 액세스 컨트롤, 정책 통제 그리고 각종 모니터링 지표의 수집이다.

예를 들어서 서비스의 총 처리량을 정책으로 지정하여, 그 처리량 이상으로 요청을 못받게 하거나 특정 헤더값이 일치해야 요청을 받을 수 있게 하는 등의 다양한 정책을 정의하고 이를 컨트롤 할 수 있다.

또한 서비스의 응답 시간이나 평균 처리량과 같은 다양한 지표를 수집하여 저장하는 역할을 한다.

시타델(Citadel)

시타델은 보안에 관련된 기능을 담당하는 모듈이다. 서비스를 사용하기 위한 사용자 인증 (Authentication)과 인가 (Authorization)을 담당한다. 또한 Istio는 통신을 TLS(SSL)을 이용하여 암호화할 수 있는데, TLS 암호화나 또는 사용자 인증에 필요한 인증서(Certification)을 관리하는 역할을 한다.  

기능

대략적인 구조를 이해했으면, Istio가 어떤 기능을 제공하는지 주요 기능을 살펴보도록 하자.

트래픽 통제

트래픽 분할

트래픽 분할은 서로 다른 버전의 서비스를 배포해놓고, 버전별로 트래픽의 양을 조절할 수 있는 기능이다. 예를 들어 새 버전의 서비스를 배포할때, 기존 버전으로 95%의 트래픽을 보내고, 새 버전으로 5%의 트래픽만 보내서 테스트하는 것이 가능하다. (카날리 테스트)


컨텐츠 기반의 트래픽 분할

단순하게 커넥션 기반으로 트래픽을 분할하는 것이 아니라, 조금 더 발전된 기능으로 네트워크 패킷의 내용을 기반으로 라우팅이 가능하다. 예를 들어 아래 우측 그림과 같이 HTTP 헤더의 User-agent 필드에 따라서, 클라이언트가 안드로이드일 경우에는 안드로이드 서비스로 라우팅을 하고, IPhone일 경우에는 IOS 서비스로 라우팅을 할 수 있다.


서비스간 안정성 제공 (Resilience)

파일럿은 트래픽 통제를 통해서 서비스 호출에 대한 안정성을 제공한다.

헬스체크 및 서비스 디스커버리

파일럿은 대상 서비스가 여러개의 인스턴스로 구성이 되어 있으면 이를 로드 밸런싱하고, 이 서비스들에 대해서 주기적으로 상태 체크를 하고, 만약에 장애가 난 서비스가 있으면 자동으로 서비스에서 제거한다.


Retry,Timeout,Circuit breaker

서비스간의 호출 안정성을 위해서, 재시도 횟수를 통제할 수 있다. 호출을 했을때 일정 시간 (Timeout)이상 응답이 오지 않으면 에러 처리를 할 수 있고, 앞에서 설명한 마이크로 서비스 아키텍쳐 패턴중 하나인 써킷 브레이커 (Circuit breaker) 패턴을 지원한다.

보안

Istio의 특징은 서비스에 대한 보안 기능을 추가해준다가는 것이다.

통신 보안

기본적으로 envoy를 통해서 통신하는 모든 트래픽을 자동으로 TLS를 이용해서 암호화한다. 즉 서비스간의 통신이 디폴트로 암호화 된다.


암호화를 위해서 인증서를 사용하는데, 이 인증서는 시타델(Citadel)에 저장되어 있는 인증서를 다운 받아서, 이 인증서를 이용하여 암호화된 TLS 통신을 한다.

서비스 인증과 인가

Istio는 서비스에 대한 인증 (Authentication)을 제공하는데, 크게 서비스와 서비스간 호출에서, 서비스를 인증하는 기능과, 서비스를 호출하는 클라이언트를 직접인증 할 수 있다.  

서비스간 인증

서비스간 인증은 인증서를 이용하여 양방향 TLS (Mutual TLS) 인증을 이용하여, 서비스가 서로를 식별하고 인증한다.

서비스와 사용자간 인증

서비스간 인증뿐 아니라, 엔드 유저 즉 사용자 클라이언트를 인증할 수 있는 기능인데, JWT 토큰을 이용해서 서비스에 접근할 수 있는 클라이언트를 인증할 수 있다.

인가를 통한 권한 통제 (Authorization)

인증뿐만 아니라, 서비스에 대한 접근 권한을 통제 (Authorization)이 가능하다. 기본적으로 역할 기반의 권한 인증 (RBAC : Role based authorization control)을 지원한다.

앞에서 인증된 사용자(End User)나 서비스는 각각 사용자 계정이나 쿠버네티스의 서비스 어카운트로 계정이 정의 되고, 이 계정에 역할(Role)을 부여해서 역할을 기반으로 서비스 접근 권한을 정의할 수 있다.


apiVersion: "rbac.istio.io/v1alpha1"

kind: ServiceRole

metadata:

 name: products-viewer

 namespace: default

spec:

 rules:

 - services: ["products.default.svc.cluster.local"]

   methods: ["GET", "HEAD"]


예를 들어 위의 역할은 products-viewer라는 권한으로 products.default.svc.cluster.local 서비스에 대해서 HTTP GET, HEAD를 지원하는 권한을 정의한것이다.

이렇게 정의된 역할 (Role)을 계정에 적용해야 하는데 아래는 products-viewer 역할을 서비스 어카운트와 엔드유저 계정에 반영한 예제이다.

apiVersion: "rbac.istio.io/v1alpha1"

kind: ServiceRoleBinding

metadata:

 name: test-binding-products

 namespace: default

spec:

 subjects:

 - user: "service-account-a"

 - user: "istio-ingress-service-account"

   properties:

     request.auth.claims[email]: "a@foo.com"

 roleRef:

   kind: ServiceRole

   name: "products-viewer"


“Service-account-a” 서비스 어카운트에 권한을 부여한 것이고, Istio의 Ingress를 통해서 들어오는 사용자 트래픽에 대해서 사용자 ID가 “a@foo.com”인 사용자에게도 같은 역할을 부여한것이다.


모니터링

마이크로 서비스에서 문제점중의 하나는 서비스가 많아 지면서 어떤 서비스가 어떤 서비스를 부르는지 의존성을 알기가 어렵고, 각 서비스를 개별적으로 모니터링 하기가 어렵다는 문제가 있다. Istio는 네트워크 트래픽을 모니터링함으로써, 서비스간에 호출 관계가 어떻게 되고, 서비스의 응답 시간, 처리량등의 다양한 지표를 수집하여 모니터링할 수 있다.

<그림. Mixer에서 서비스 관련 지표를 수집하는 구조>


서비스 A가 서비스 B를 호출할때 호출 트래픽은 각각의 envoy 프록시를 통하게 되고, 호출을 할때, 응답 시간과 서비스의 처리량이 Mixer로 전달된다. 전달된 각종 지표는 Mixer에 연결된 Logging Backend에 저장된다.


Mixer는 위의 그림과 같이 플러그인이 가능한 아답터 구조로, 운영하는 인프라에 맞춰서 로깅 및 모니터링 시스템을 손쉽게 변환이 가능하다.  쿠버네티스에서 많이 사용되는 Heapster나 Prometheus에서 부터 구글 클라우드의 StackDriver 그리고, 전문 모니터링 서비스인 Datadog 등으로 저장이 가능하다.

이렇게 저장된 지표들은 여러 시각화 도구를 이용해서 시각화 될 수 있는데, 아래 그림은 Grafana를 이용해서 서비스의 지표를 시각화 한 그림이다.





그리고 근래에 소개된 오픈소스 중에서 흥미로운 오픈 소스중의 하나가 Kiali (https://www.kiali.io/)라는 오픈소스인데, Istio에 의해서 수집된 각종 지표를 기반으로, 서비스간의 관계를 아래 그림과 같이 시각화하여 나타낼 수 있다.  아래는 그림이라서 움직이는 모습이 보이지 않지만 실제로 트래픽이 흘러가는 경로로 에니메이션을 이용하여 표현하고 있고, 서비스의 각종 지표, 처리량, 정상 여부, 응답 시간등을 손쉽게 표현해준다.





다음 글에서는 쿠버네티스 클러스터에 Istio를 설치해보고, 마이크로 서비스를 배포해보도록 하겠다.

본인은 구글 클라우드의 직원이며, 이 블로그에 있는 모든 글은 회사와 관계 없는 개인의 의견임을 알립니다.

댓글을 달아 주세요

  1. 김해마 2020.02.12 18:06  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 쿠버네티스를 운영하다가 istio를 통해 prometheus 를 add-on으로 설치하였습니다. 문제는 작은 규모의 클러스터에서는 잘 작동하는데 운영환경(250개가 넘는 pods)에서는 prometheus에서 수집하는 메트릭이 많아져서 memory 가 설정에 걸어논 limit 까지 가다가 limit에 도달하면 죽어버립니다. 프로메테우스 pod에 HPA를 memory policy로 걸어서 돌려보았는데 memory 사용량이 증가하면 설정 값에 따라서 pods가 여러노드로 scale-out 됩니다. 여기서 궁금한 포인트가, prometheus는 scale-out이 가능한 형태가 아닌데 제가 한 설정이 잘못된 것인지요, 아니면 블로그에 적어 놓으신 것 처럼 타노스라고 하는 다른 솔루션을 사용해야 하는지요. 궁금해서 여쭙습니다..

  2. 조대협 2020.02.13 11:38 신고  댓글주소  수정/삭제  댓글쓰기

    프로메테우스는 스케일 아웃이 안되는 구조입니다. Pod의 크기 (CPU/메모리/디스크)를 늘리셔야 합니다.