주제
- recursion 과 stack overflow
- 분할 정복 – 카라츠바 알고리즘
주제
고대 그리스어로 조타수? 키잡이? 라는 의미라고 한다.
구글에서 개발한 container orchestration tool(vm 또는 container 관리를 자동화해주는 도구)이다.
사실 이 부분은 충분한 경험이 없어서 자신있게 말을 못하겠다.
온라인에서 이것 저것 찾아보면 몇 가지 후보군이 더 있다.
이 중에서 kubernetes 를 선택한 이유는
기본적으로 제공하는 기능과 설계구조가 전반적으로 낫다는 평(카더라)
업계의 선택 – 실무 활용 사례가 많다는 점
때문이다. 다른 오케스트레이션 툴 중에 더 나은 것이 있다면 얼마든지 변경할 수 있다.
는 생략… 공식 홈페이지에 tutorial 이 잘 나와 있다.
클러스터링 전략을 어떻게 가져가느냐에 따라 다양한 방법으로 설치가 가능하므로
문서를 잘 읽어보고 본인의 환경에 맞게 설치하길 바란다.
처음 시작하는 사람들은 로컬 개발환경에 docker / minikube / kubectl 설치하면 된다.
https://kubernetes.io/docs/getting-started-guides/minikube/
아래와 같이 간단하게 동작하는 flask app을 만들자.
중요!
host 설정을 0.0.0.0 으로 하여야 가상 컨테이너 외부에서 별도 proxy 없이 접근 가능하다.
from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello!!" if __name__ == "__main__": app.run(host='0.0.0.0')
특별한 설정 없이 실행하는 것 만으로 웹 서비스를 제공한다.
$ python3 hello.py * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) 127.0.0.1 - - [11/Apr/2017 08:40:53] "GET / HTTP/1.1" 200 -
이 flask app 을 docker image 로 만들어 보자.
다음과 같이 Dockerfile 을 만들자.
기존에 제공된 python image 기반으로 조금 전에 만든 flask app 을 복사/실행하도록 하자.
FROM python:3.6 EXPOSE 5000 COPY hello.py . RUN pip install flask CMD ["python", "./hello.py"]
만든 Dockerfile로부터 docker image 를 만들자.
$ docker build -t hello-flask:v1 .
이 docker image 가 잘 동작하는지 확인해 보자.
docker run -it --rm -p 5000:5000 hello-flask:v1 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) 172.17.0.1 - - [10/Apr/2017 23:54:50] "GET / HTTP/1.1" 200 -
다음 2가지를 주의한다.
kubernetes 는 Dockerfile 로부터 container를 생성하는 기능은 제공하지 않는다. docker image를 미리 만들어 두어야 한다.
kubernetes 하위에서 실행되는 docker 환경은 시스템에 설치된 docker 환경과는 완전히 독립적이다. docker image / container 를 공유하지 않으므로 image를 다시 만들어야 한다.
$ eval $(minikube docker-env) //kubernetes docker 실행환경 전환 $ eval $(minikube docker-env -u) //kubernetes docker 실행환경 원복
kubernetes docker 실행환경으로 전환하여 docker image 를 만든 후, kubectl을 이용하여 다음과 같이 deployment(pod) 를 생성한다.
$ kubectl run hello-flask --image=hello-flask:v1 --port=5000 deployment "hello-flask" created
kubectl 을 이용해 deployment / pod 정보를 보면 아래와 같다.
$ kubectl get deployments NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE hello-flask 1 1 1 1 24s $ kubectl get pods NAME READY STATUS RESTARTS AGE hello-flask-2475082718-xw1s3 1/1 Running 0 3m
kubernetes 는 container / container group 의 단위로 pod 를 사용한다.
반면 deployment 는 pod 의 상태를 체크하여 pod 생성/재시작/scaling 을 담당한다.
pod 가 Object 라면 deployment 는 Factory 나 Builder 와 같은 관계랄까.
위와 같은 이유로 보통 kubectl 로 container 를 생성/삭제할 때에는 deployment 로 호칭하므로 참고하기 바란다.
deployment 만 생성하여서는 아직 서비스를 할 수가 없다.
다음과 같이 kubectl 명령을 사용하여 deployment 를 service 로 실행한다.
kubectl expose deployment hello-flask --type=LoadBalancer --name=hello-flask
kubectl 을 이용해 service 정보를 확인해 보자
$ kubectl get service hello-flask NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE hello-flask 10.0.0.245 <pending> 5000:30276/TCP <invalid> $ kubectl describe service hello-flask Name: hello-flask Namespace: default Labels: run=hello-flask Selector: run=hello-flask Type: LoadBalancer IP: 10.0.0.245 Port: <unset> 5000/TCP NodePort: <unset> 30276/TCP Endpoints: 172.17.0.2:5000 Session Affinity: None No events.
다음 minikube 명령을 이용하면 flask app 이 어느 external ip 와 mapping 되어 있는지 브라우저로 확인할 수 있다. 현재 별도로 kubernetes 에게 external ip/port 할당 관련한 설정을 하지 않았으므로 아무렇게나 할 것이다.
$ minikube service hello-flask Opening kubernetes service default/hello-flask in default browser...
다음과 같이 stdout log 도 확인 가능하다.
$ kubectl logs -f hello-flask-2475082718-xw1s3 * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) 172.17.0.1 - - [11/Apr/2017 00:36:00] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:36:00] "GET /favicon.ico HTTP/1.1" 404 - 172.17.0.1 - - [11/Apr/2017 00:38:15] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:40:47] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:42:22] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:47:26] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:47:27] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:47:27] "GET / HTTP/1.1" 200 - 172.17.0.1 - - [11/Apr/2017 00:47:27] "GET / HTTP/1.1" 200 -
중요!
container 기반 app 을 작성할 때에는 log를 반드시 stdout 으로 남기도록 한다.
그래야 log 를 container 와 별도로 수집 가능하다.
금번 스터디는 구종만 님의 매우 명저 “알고리즘 문제해결 전략” 으로 8주간 진행합니다.
개인적으로는 분량도 짧고(!) 내용도 알찬 한주영 님의 “개미수열을 푸는 10가지 방법”으로 진행하길 원했으나 다수결에 밀려(ㅜㅜ) “알고리즘 문제해결 전략” 으로 선정되었습니다.
사실 “알고리즘 문제해결전략”은 매우 명저이고 저도 참 좋아하는 책입니다. 저자분이 그 유명한 알고스팟 운영자시기도 하구요. 많은 분들께서 아주 의욕적으로 알고리즘 주제를 깊이 있게 공부하길 원한다는 의미인 듯 하여 매우 기쁩니다.
아래는 스터디 ppt 입니다.
rxjava 가 메이저 버전 업(1->2)을 하면서 몇 가지 변경점이 생겼다.
변경점에 대한 자세한 내용은 아래 링크를 참조하기 바란다.
Flowable 이라는 base reactive class 가 추가 되었다. Observable 과의 차이는 backpressure buffer의 기본 탑재 유무이다.
우리말로 번역하면 ‘등 뒤에서 떠밀리는 압박’ 정도가 될 듯 하다.
이런 상황을 가정해보자. 콘서트장을 사람들이 가득 메웠다. 콘서트장에 들어오려는 사람들은 저글링 개떼처럼 밀려드는데 나가는 사람은 별로 없다. 콘서트장 출입구를 통제하는 요원이 없다면? 콘서트장이 터지던지 안에 있던 사람들이 짜부러지던지 아무튼 대형 사고가 발생할거다.
publish / subscribe 모델에서도 이런 비극적인 시나리오가 발생할 수 있다. 생산자는 미친듯이 element 를 생산해 내는데 소비자가 처리하는 속도가 이를 따라가지 못한다면
‘등 뒤에서 떠밀리는 압박’ 에 대한 흐름제어를 위한 버퍼가 바로 backpressure buffer 다. 버퍼가 가득 차면 어차피 소비자는 element 를 처리할 여유가 없는 상태이므로 더 이상 publish 를 하지 않는다.
기존에 없던 개념이 새로 추가된 것은 아니다. 기존 rxJava 1.xx 의 경우 Observable 에 backpressure buffer 를 직접 생성해 주면 사용이 가능하다. 허나 rxJava 개발자는 초보자들이 미처 알아채지 못하는 영역에서 기대하지 않는 동작이 일어날 가능성이 있다며 Flowable 을 추가하였다.
다음 예제코드를 보자. 생산자의 생산 속도를 소비자가 따라가지 못하는 시나리오다.
Flowable 을 사용하면 default buffer size(128) 이상 backpressure buffer 에 element 가 쌓일 경우 흐름제어를 한다.
public class example01 { public static void main(String... args) throws InterruptedException { final String tmpStr = Arrays.stream(new String[10_000_000]).map(x->"*").collect(Collectors.joining()); Flowable foo = Flowable.range(0, 1000_000_000) .map(x-> { System.out.println("[very fast sender] i'm fast. very fast."); System.out.println(String.format("sending id: %s %d%50.50s", Thread.currentThread().getName(), x, tmpStr)); return x+tmpStr; }); foo.observeOn(Schedulers.computation()).subscribe(x->{ Thread.sleep(1000); System.out.println("[very busy receiver] i'm busy. very busy."); System.out.println(String.format("receiving id: %s %50.50s", Thread.currentThread().getName(), x)); }); while (true) { Thread.sleep(1000); } } }
[very fast sender] i'm fast. very fast. sending id: main 0************************************************** [very fast sender] i'm fast. very fast. sending id: main 1************************************************** [very fast sender] i'm fast. very fast. sending id: main 2************************************************** [very fast sender] i'm fast. very fast. sending id: main 3************************************************** [very fast sender] i'm fast. very fast. sending id: main 4************************************************** [very fast sender] i'm fast. very fast. sending id: main 5************************************************** [very fast sender] i'm fast. very fast. sending id: main 6************************************************** [very fast sender] i'm fast. very fast. sending id: main 7************************************************** [very fast sender] i'm fast. very fast. sending id: main 8************************************************** [very fast sender] i'm fast. very fast. sending id: main 9************************************************** [very fast sender] i'm fast. very fast. sending id: main 10************************************************** ... 중략 ... [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 0************************************************* receiving id: RxComputationThreadPool-1 1************************************************* receiving id: RxComputationThreadPool-1 2************************************************* [very fast sender] i'm fast. very fast. sending id: main 117************************************************** [very fast sender] i'm fast. very fast. sending id: main 118************************************************** [very fast sender] i'm fast. very fast. sending id: main 119************************************************** [very fast sender] i'm fast. very fast. sending id: main 120************************************************** [very fast sender] i'm fast. very fast. sending id: main 121************************************************** [very fast sender] i'm fast. very fast. sending id: main 122************************************************** [very fast sender] i'm fast. very fast. sending id: main 123************************************************** [very fast sender] i'm fast. very fast. sending id: main 124************************************************** [very fast sender] i'm fast. very fast. sending id: main 125************************************************** [very fast sender] i'm fast. very fast. sending id: main 126************************************************** [very fast sender] i'm fast. very fast. sending id: main 127************************************************** [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 3************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 4************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 5************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 6************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 7************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 8************************************************* [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 9*************************************************
반면, 같은 시나리오를 Observable 을 backpressure buffer 생성 없이 사용하면 OutOfMemoryException 이 발생한다.
public class example02 { public static void main(String... args) throws InterruptedException { final String tmpStr = Arrays.stream(new String[10_000_000]).map(x->"*").collect(Collectors.joining()); Observable foo = Observable.range(0, 1000_000_000) .map(x-> { System.out.println("[very fast sender] i'm fast. very fast."); System.out.println(String.format("sending id: %s %d%50.50s", Thread.currentThread().getName(), x, tmpStr)); return x+tmpStr; }); foo.observeOn(Schedulers.computation()).subscribe(x->{ Thread.sleep(1000); System.out.println("[very busy receiver] i'm busy. very busy."); System.out.println(String.format("receiving id: %s %50.50s", Thread.currentThread().getName(), x)); }); while (true) { Thread.sleep(1000); } } }
[very fast sender] i'm fast. very fast. sending id: main 0************************************************** [very fast sender] i'm fast. very fast. sending id: main 1************************************************** [very fast sender] i'm fast. very fast. sending id: main 2************************************************** [very fast sender] i'm fast. very fast. sending id: main 3************************************************** [very fast sender] i'm fast. very fast. sending id: main 4************************************************** [very fast sender] i'm fast. very fast. sending id: main 5************************************************** [very fast sender] i'm fast. very fast. sending id: main 6************************************************** [very fast sender] i'm fast. very fast. sending id: main 7************************************************** [very fast sender] i'm fast. very fast. sending id: main 8************************************************** [very fast sender] i'm fast. very fast. sending id: main 9************************************************** [very fast sender] i'm fast. very fast. sending id: main 10************************************************** [very fast sender] i'm fast. very fast. ...중략... sending id: main 198************************************************** [very fast sender] i'm fast. very fast. sending id: main 199************************************************** [very fast sender] i'm fast. very fast. sending id: main 200************************************************** Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOfRange(Arrays.java:3664) at java.lang.String.<init>(String.java:207) at java.lang.StringBuilder.toString(StringBuilder.java:407) at example02.lambda$main$1(example02.java:24) at example02$$Lambda$6/123961122.apply(Unknown Source) at io.reactivex.internal.operators.observable.ObservableMap$MapObserver.onNext(ObservableMap.java:59) at io.reactivex.internal.operators.observable.ObservableRange$RangeDisposable.run(ObservableRange.java:64) at io.reactivex.internal.operators.observable.ObservableRange.subscribeActual(ObservableRange.java:35) at io.reactivex.Observable.subscribe(Observable.java:10700) at io.reactivex.internal.operators.observable.ObservableMap.subscribeActual(ObservableMap.java:33) at io.reactivex.Observable.subscribe(Observable.java:10700) at io.reactivex.internal.operators.observable.ObservableObserveOn.subscribeActual(ObservableObserveOn.java:45) at io.reactivex.Observable.subscribe(Observable.java:10700) at io.reactivex.Observable.subscribe(Observable.java:10686) at io.reactivex.Observable.subscribe(Observable.java:10589) at example02.main(example02.java:27) [very busy receiver] i'm busy. very busy. receiving id: RxComputationThreadPool-1 5*************************************************
참고로, Flowable 은 FlowableCreate 라는 builder 에서 생성되며, 특별한 설정이 없을 경우 buffer size 는 최소 16, 기본 128 로 설정한다.
//FlowableCreate.java line:44 @Override public void subscribeActual(Subscriber<? super T> t) { BaseEmitter<T> emitter; switch (backpressure) { case MISSING: { emitter = new MissingEmitter<T>(t); break; } case ERROR: { emitter = new ErrorAsyncEmitter<T>(t); break; } case DROP: { emitter = new DropAsyncEmitter<T>(t); break; } case LATEST: { emitter = new LatestAsyncEmitter<T>(t); break; } default: { emitter = new BufferAsyncEmitter<T>(t, bufferSize()); break; } } t.onSubscribe(emitter); try { source.subscribe(emitter); } catch (Throwable ex) { Exceptions.throwIfFatal(ex); emitter.onError(ex); } }
//Flowable.java line:61 static final int BUFFER_SIZE; static { BUFFER_SIZE = Math.max(16, Integer.getInteger("rx2.buffer-size", 128)); }
코딩 소림사 정기 세미나 발표자료
4년 전 현재 직장입사 후 약 한 달 정도 함께 근무하였던 임도형 님의 예외처리에 대한 명 ppt 2개를 소개한다.
첫번째 ppt 는 java 예외 처리의 원칙에 대한 내용이고
두번째 ppt 는 언제 어디에 예외를 남길지에 대한 내용이다.
요약하면
부연하자면
Exception 이란 정의되지 않은 동작을 처리하는 규약이다. 개발자가 정의한 io 의 범주를 벗어날지라도 우리는 이를 처리하는 규약을 정의할 수 있다.
java 에는 크게 두 가지 Exception 이 존재한다.
CheckedException 의 경우, 처리의 기준이 명백하고 프로그램은 복구가 가능하다. 처리에 크게 고민할 필요가 없다.
문제는 RuntimeException이다. RuntimeException이 발생하였다는 것은 이 프로그램이 복구 불가능한 상태에 진입하였다는 의미이다. 아래 소개된 임도형 님의 ppt 는 RuntimeException 을 어떻게 잘 처리할지에 대한 내용이다. Exception 처리같은 경우 충분한 경험이 없다면 매끄럽게 해결하기가 어려운데, 이 ppt는 실천적인 가이드를 제시해주는 매우 훌륭한 자료이므로 java 개발자라면 반드시 한번은 읽어볼 필요가 있다.
아이들 코딩 교육을 어떻게 하는게 재미있을까 이것 저것 알아보다 결국 minecraft 로 결정
minecraft로 여러가지 재미있는 일을 할 수 있다(..고한다.) 그 중에 하나가 malmo project 인데, 개인이 쉽게 구성하기 어려운 unsupervised learning 환경을 minecraft 로 제공하는 프로젝트다.
이 글에선 malmo 는 다루지 않고 minecraft – python 인터페이스를 어떻게 구성하는지만 정리한다.
python 은 설치되어 있어야 한다.
python 실행환경이 minecraft 에서 접근 가능해야 한다.
설치 : https://minecraft.net/ko-kr/
주의할 점: windows 10 edition 은 설치하지 말 것. python 인터페이스를 구성할 수 없음
현재 설치할 수 있는 가장 최신 버전은 1.11.2 이다.
minecraft mod 를 설치할 수 있도록 도와주는 tool
처음 설치했던 minecraft와 같은 버전으로 설치한다.
설치 : https://files.minecraftforge.net/
본래는 raspberrypi 에서 minecraft 를 동작시키기 위한 mod.
허나 이 mod 를 설치하면 python 인터페이스를 바로 구성할 수 있다.
설치 : https://github.com/arpruss/raspberryjammod/releases
확인
minecraft 를 실행한다.
실행환경이 forge 로 되어있는지 확인한다.
%APPDATA%.minecraft\mcpipy 디렉토리 안에 포함된 예제 코드 중 아무거나 minecraft 안에서 실행해 본다.
실행 방법은 /py [python filename] 이다.
readme 만 잘 읽고 따라하면 특별할 것은 없다.
근데 정말 잘(!) 읽어야 하므로 주의사항을 기록해 둔다.
build tool
bezel 과 cmake 중 하나를 선택할 수 있다.
bezel 이 더 좋아보이긴 하지만 윈도우 환경을 제대로 지원하지 않으므로 mingw 를 설치하여야 한다. 그러니 왠만하면 cmake 로 빌드하자.
prerequisite
swig
python 3.5
visual studio 2015
(중요) cmake 3.5~3.6
nvidia cuda toolkit 8.0
nvidia cudnn 5.1
여기까지만 구성하면 나머지는 readme 의 설명대로 따르면 된다.
cmake 버전만 조심하자.
python 이해도를 높이기 위한 목적으로,
잘 만들어진 library 소스코드(tensorflow)를 보다가 특기할 만한 짧은 내용들을 간략히 소개한다.
__future__
compiler 지시자로 동작한다.
하위 호환성이 없는 기능을 미리 사용하기 위함이다.
링크 : PEP 236 – Back to the __future__
링크 : future statement
__all__
init.py 내에서 작성하는 variable 이다.
package 내의 공개 가능한 submodule 의 범위를 한정하기 위함이다.
복잡한 프로젝트의 구조와 별개로 사용자 인터페이스를 단순하게 하고 싶을 때 사용할 수도 있다.
# tensorflow\__init__.py # tensorflow.python 패키지 하위의 submodule 들을 사용자가 접근할 때 # tensorflow.xxx 으로 사용 가능하도록 한다. from tensorflow.python import *
링크 : importing * from a package
pylint
정적분석 도구다.
PEP8 스타일 가이드를 따르도록 도와준다.
# pylint: disable=wildcard-import from tensorflow.python import * # pylint: enable=wildcard-import
링크 : PEP8 style guilde for python code
SWIG
python – native code wrapper 다.
같은 목적의 도구로 boost::python 가 있다.
둘 다 써보지 않아서 잘은 모르겠지만
SWIG 는 별도의 인터페이스 file 을 작성하여야 하는 번거로움이 있는 대신
python / tcl / perl / java /c# 등 다양한 타겟으로 인터페이스가 가능하다.
boost 빠인 나로선 boost::python 쪽에 더 마음이 간다.