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


Archive»


 
 



프로메테우스 #3. 그라파나를 이용한 시각화

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


그라파나(Grafana)는 메트릭을 시각화 해주는 오픈소스 도구이다. Graphite, Prometheus, InfluxDB등 다양한 데이타베이스와 메트릭수집 시스템을 지원하고, 하나의 대쉬보드에 동시에 여러 메트릭 시스템들의 지표를 표시할 수 있고 무엇보다 설치 및 사용 방법이 쉽기 때문에 널리 사용되고 있다

특히 프로메테우스를 잘 지원하고 있기 때문에, 프로메테우스의 메트릭을 그래프로 시각화 하는데도 많이 사용된다. 

그라파나의 설치는 비교적 간단한 편이기 때문에 여기서는 별도로 설명하지 않는다. 설치 방법은 공식 문서 https://grafana.com/docs/grafana/latest/installation/debian/ 를 참고하기 바란다. 

이 문서에서 사용한 테스트 환경은 katakoda.com에서 그라파나 튜토리얼 환경을 이용하였다. https://www.katacoda.com/courses/prometheus/creating-dashboards-with-grafana


앞의 문서 (https://bcho.tistory.com/1373) 에 따라서 프로메테우스를 설치하였으면, 그라파나를 설치한다. 

다음 그라파나 웹 관리 화면으로 접속을 하고 로그인을 하면 아래와 같은 화면을 볼 수 있다. 



이 화면에서 Add Data source를 선택해서 프로메테우스 서버를 새로운 데이타 소스로 등록할것이다.

아래 메뉴에서 데이타 소스의 이름을 “Prometheus”로 설정하고, 타입을 Prometheus로 선택한다.

다음 Http Setting 부분의 Url 부분에, Prometheus 서버의 주소를 적어넣는다. 이 예제에서는 로컬에 프로메테우스를 기동 시켰기 때문에, http://localhost:9090 을 입력하면 된다. 



이제 프로메테우스 서버와 그라파나가 연결되었다. 

이제 그래프를 그려볼 예정인데, 초기 화면으로 돌아가서 “Create your first dash board” 메뉴를 선택한다. 다음 New Dash 보드 메뉴를 선택하여 비어 있는 대쉬 보드를 하나 만든다. 



다음에 그래프를 하나 선택하면 아래와 같이 빈 그래프가 나오는데, 이 그래프를 프로메테우스의 메트릭과 연결할것이다. 




그래프에서 상단의 “Panel Title” 를 누르면 아래 그림과 같이 메뉴가 나오는데, 여기서 “Edit” 메뉴를 선택한다.  그러면 아래와 같이 설정을 할 수 있는 화면이 나오는데, 여기서 “Metric” 메뉴를 선택한다.




Metric 메뉴에서는 시각화 하고 싶은 프로메테우스의 필드를 선택하면 되는데, PrometheusQL을 사용해서 정의할 수 도 있다. 여기서는 간단하게 node의 CPU정보를 시각화 하기 위해서 node_cpu 를 선택하였다. 그러면 아래와 같이 node_cpu 메트릭에 대한 정보를 그래프로 그려주는 것을 확인할 수 있다. 



그라파나에서는 널리 사용되는 시스템에 대한 대쉬보드를 템플릿 형태로 만들어서 사용자들이 서로 공유할 수 있도록 하는데, 템플릿은  https://grafana.com/dashboards 에 가면 찾아볼 수 있다.

여기서는 프로메테우스 node_exporter에 의해서 제공되는 메트릭을 모니터링할 수 있는 대쉬보드를 import 해서 사용해보자. 


초기화면에서 대쉬보드 생성 메뉴로 들어 간 후에, 아래 그림에서 import dashboard 메뉴를 선택하자


그러면 아래와 같이 import 화면이 나오는데, 대쉬 보드 설정을 json 파일로 업로드 할 수 도 있지만, 그라파나 대쉬보드 웹사이트에 있는 경우에는 아래 처럼 URL이나 dashboard id를 넣으면 된다.

아래 그림처럼 https://grafana.com/dashboards/22 를 필드에 입력한다.



로딩된 대쉬 보드를 보면 다음과 같다.





지금까지 프로메테우스에 대한 소개와 내부 구조, 그리고 간단한 사용법 및 시각화 방법에 대해서 알아보았다. 이 정도면 기본적인 모니터링 시스템 구성에는 문제가 없지만, 프로메테우스는 앞선 글에서도 언급 하였듯이 싱글 서버가 기동되는 구조이기 때문에, 확장성과 장애에 취약한 단점을 가지고 있는데, 다음글에서는 이 문제를 어떻게 해결할 수 있는지에 대해서 알아보도록 하겠다.

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

댓글을 달아 주세요

Locust 와 쿠버네티스를 이용한 분산 부하 테스트

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

분산 부하 테스트

locust는 여러개의 worker를 이용하여, 부하를 대량으로 발생 시키는 분산 부하 테스트가 가능하다. 특히 분산 클러스터 구성 설정이 매우 간단하다는 장점을 가지고 있다. 


마스터 노드의 경우에는 아래와 같이 --master 옵션을 지정하여 마스터 노드로 구동하면 되고, 

% locust -f {task file name} --host={target host address} --master


워커 노드의 경우에는 실행 모드를 slave로 하고, 마스터 노드의 주소만 명시해주면 된다. 

% locust -f {task file name} --host={target host address} --slave --master-host={master node의 주소}


이렇게 클러스터를 구성하면 아래와 같은 구조를 갖는다


특히 워크노드를 추가할때 별도의 설정이 필요없이 워커노드를 추가하면서 마스터 노드의 주소만 주면 되기 때문에, 스케일링이 편리하다. 

쿠버네티스에서 분산 부하 테스트

설정이 간단하고, 부하 테스트의 특성상 필요할때만 클러스터를 설치했다가 사용이 끝나면 없애는 구조로 사용을 하려면, 쿠버네티스에서 기동하면 매우 편리하다. 


특히 설치 및 설정을 Helm Chart로 만들어놓으면, 필요할때 마다 손쉽게 locust 클러스터를 설치했다가 사용이 끝나면 손쉽게 지울 수 있는데, Helm 공식 리파지토리에 등록된 locust chart는 https://github.com/helm/charts/tree/master/stable/locust 아쉽게도 locust 0.9를 기준으로 한다. 현재는 1.3 버전이 최신 버전인데, 0.9와 1.3은 설정고 TaskSet의 코딩 방식이 다소 변경되서, locust 0.9를 사용하게 되면, 현재 메뉴얼에 있는 일부 코드들을 사용할 수 없다. 

그래서, 스스로 Helm Chart를 만들어서 사용하기를 권장한다. 


좋은 소식중의 하나는 구글 클라우드에서 Locust 를 쿠버네티스에서 분산 부하 테스팅을 할 수 있는 가이드 문서를 제공하고 있으니, 이를 참조하면 손쉽게 사용이 가능하다.

https://cloud.google.com/solutions/distributed-load-testing-using-gke

단, helm chart 형태로는 제공하지 않으니, 스스로 Chart를 만들기를 권장한다. Chart를 만들때 기존 Chart 구조를 참고하기를 권장하는데, https://github.com/helm/charts/tree/master/stable/locust 를 보면, locust의 이미지 버전이나, 설정을 Helm의 values.yaml로 빼거나 또는 CLI parameter로 처리할 수 있게 하였고, 특히 아래 task files를 설정하는 방법이 매우 흥미로운데, 


task file을 도커 이미지 내에 넣는 방식이 아니라 file (또는 directory) 형태의 configmap으로 빼서, locust pod가 기동될때, 이 파일(디렉토리)를 마운트해서 스크립트를 읽어서 적용하는 구조를 가지고 있다.


위에서 언급한 구글 클라우드 플랫폼의 가이드는 task 파일을 docker 이미지로 매번 빌드하기 때문에, 다소 설치가 복잡해질 수 있다는 단점이 있다. 


그러면 구글 클라우드 플랫폼에서 제공하는 예제 파일들을 살펴보자

https://github.com/GoogleCloudPlatform/distributed-load-testing-using-kubernetes 리포에 저장이 되어 있는데, 

/docker-image 디렉토리

이 디렉토리에는 locust 도커 컨테이너를 만들기 위한 설정 파일들이 들어 있다. 

Dockerfile의 내용을 보면 아래와 같다.

# Start with a base Python 3.7.2 image

FROM python:3.7.2

 

# Add the licenses for third party software and libraries

ADD licenses /licenses

 

# Add the external tasks directory into /tasks

ADD locust-tasks /locust-tasks

 

# Install the required dependencies via pip

RUN pip install -r /locust-tasks/requirements.txt

 

# Expose the required Locust ports

EXPOSE 5557 5558 8089

 

# Set script to be executable

RUN chmod 755 /locust-tasks/run.sh

 

# Start Locust using LOCUS_OPTS environment variable

ENTRYPOINT ["/locust-tasks/run.sh"]


주의 깊게 살펴볼 부분은 아래 두줄이다. 

ADD locust-tasks /locust-tasks

RUN pip install -r /locust-tasks/requirements.txt


첫번째는 locust task 스크립트를 /locust-tasks 디렉토리에 복사하는 부분이다. 

그리고 두번째는 스크립트에서 사용하는 파이썬 라이브러리를 설치하는 부분이다. 

kubernetes-config 디렉토리


locust-master-controller.yaml

apiVersion: "extensions/v1beta1"

kind: "Deployment"

metadata:

 name: locust-master

 labels:

   name: locust-master

spec:

 replicas: 1

 selector:

   matchLabels:

     app: locust-master

 template:

   metadata:

     labels:

       app: locust-master

   spec:

     containers:

       - name: locust-master

         image: gcr.io/[PROJECT_ID]/locust-tasks:latest

         env:

           - name: LOCUST_MODE

             value: master

           - name: TARGET_HOST

             value: https://[TARGET_HOST]

         ports:

           - name: loc-master-web

             containerPort: 8089

             protocol: TCP

           - name: loc-master-p1

             containerPort: 5557

             protocol: TCP

           - name: loc-master-p2

             containerPort: 5558

             protocol: TCP


이 파일은 master node를 배포하기 위한 deployment 정의다. 주의 깊게 살펴볼 부분은 아래 두 라인이다. image는 앞에서 만든 task 스크립트가 포함된 locust 도커 이미지를 정의 하는 부분이고, 두번째는 부하 테스트 대상이 되는 타겟 호스트 주소를 정의하는 부분이다. 


image: gcr.io/[PROJECT_ID]/locust-tasks:latest

value: https://[TARGET_HOST]


다음은 locust-master-service.yaml 파일이다.

kind: Service

apiVersion: v1

metadata:

 name: locust-master

 labels:

   app: locust-master

spec:

 ports:

   - port: 8089

     targetPort: loc-master-web

     protocol: TCP

     name: loc-master-web

   - port: 5557

     targetPort: loc-master-p1

     protocol: TCP

     name: loc-master-p1

   - port: 5558

     targetPort: loc-master-p2

     protocol: TCP

     name: loc-master-p2

 selector:

   app: locust-master

 type: LoadBalancer


앞서 정의한 master node를 External IP (Load Balancer)로 밖으로 빼서, 웹 콘솔 접속등을 가능하게 하는 설정인데, 주의할점은 locust 웹 콘솔은 별도의 사용자 인증 기능이 없기 때문에, 위의 설정을 그대로 사용하면, 인증 기능이 없는 웹콘솔이 인터넷에 그래도 노출이 되기 때문에, 권장하지 않는다.


ClusterIP로 Service를 정의한 후, kubectl의 port forwarding 기능을 사용하거나 또는 앞에 인증용 프록시 서버를 두는 것을 권장한다. 구글 클라우드의 경우에는 Identity Aware Proxy(IAP)라는 기능을 이용하여, 구글 클라우드 계정으로 로그인을 해야 웹사이트를 접근할 수 있는 프록시 기능을 제공한다. 


마지막으로 locust-worker-controller.yaml 파일을 정의한다.

# Copyright 2015 Google Inc. All rights reserved.

#

# Licensed under the Apache License, Version 2.0 (the "License");

# you may not use this file except in compliance with the License.

# You may obtain a copy of the License at

#

#     http://www.apache.org/licenses/LICENSE-2.0

#

# Unless required by applicable law or agreed to in writing, software

# distributed under the License is distributed on an "AS IS" BASIS,

# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

# See the License for the specific language governing permissions and

# limitations under the License.

 

apiVersion: "extensions/v1beta1"

kind: "Deployment"

metadata:

 name: locust-worker

 labels:

   name: locust-worker

spec:

 replicas: 5

 selector:

   matchLabels:

     app: locust-worker

 template:

   metadata:

     labels:

       app: locust-worker

   spec:

     containers:

       - name: locust-worker

         image: gcr.io/[PROJECT_ID]/locust-tasks:latest

         env:

           - name: LOCUST_MODE

             value: worker

           - name: LOCUST_MASTER

             value: locust-master

           - name: TARGET_HOST

             value: https://[TARGET_HOST]

 


이 파일을 worker node 에 대항 설정으로 master node와 마찬가지로 target_host와 locust 이미지 경로만 지정해주면 된다. 


이 파일들을 배포하면, locust 가 실행 준비가 되고, 앞에서 정의한 master node의 서비스 주소로 접속하면 locust 웹 콘솔을 이용하여 부하 테스트를 진행할 수 있다. 


만약 동적으로 locust worker node의 수를 조절하고 싶으면, 

% kubectl scale deployment/locust-worker --replicas=20


명령을 이용하여 worker node pod의 수를 조절할 수 있다. 

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

댓글을 달아 주세요

부하테스트를 위한 Locust

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


백앤드 개발을 하다보면 많이 사용되는 도구 중의 하나가 부하 테스트 툴인데, 대표적인 도구로는 Apache Jmeter, nGrinder,SOAP UI 등의 도구가 있지만 다소 사용이 어렵고 스케일링을 하는데 어려움이 있는데, locust라는 도구는 설치와 사용이 편리하고, 테스트 시나리오를 파이썬 스크립트로 작성을 하기 때문에 다양한 시나리오 구현이 가능하다. 특히 쿠버네티스에 쉽게 배포할 수 있도록 Helm으로 패키지화가 되어 있기 때문에, 필요한 경우 대규모 부하테스트 환경을 설치하고 테스트가 끝나면 쉽게 지워버릴 수 있다. 

(참고 : locust는 영어로 메뚜기라는 뜻인데, 부하를 주는 것을 swarming 이라고 표현하는게 메뚜기 떼로 부하를 비교한것이 재미있다.)


퀵스타트

간단하게 설치 및 테스트를 해보자. 

설치

설치는 파이썬만 설치되어 있으면 간단하게 pip 명령을 이용해서 설치 할 수 있다.

%python -m pip install locust

명령으로 설치 하면 된다.

설치 확인은 locust --version 명령으로 확인하면 된다.  이 방식이외에도, 이미 빌드된 도커 이미지를 사용해서 테스트를 하거나 또는 쿠버네티스에서 Helm으로 설치해서 실행하는 방법도 가능하다. 쿠버네티스에서 Helm을 사용하는 방법은 나중에 자세하게 다루도록 한다. 

간단한 부하 스크립트 생성

locust 부하테스트는 파이썬 스크립트를 실행하는 방식으로 하기 때문에, 부하테스트용 스크립트 파일을 작성해야 한다. 아래는 대상 호스트의 / 에 HTTP GET으로 요청을 보내는 스크립트이다. 스크립트 작성방법은 후에 다시 설명하도록 한다. 


from locust import HttpLocust,TaskSet,task,between

class MyTaskSet(TaskSet):

   @task

   def index(self):

       self.client.get("/")

class MyLocus(HttpLocust):

   task_set = MyTaskSet

   wait_time = between(3,5)

실행하기

locust 실행은 locust -f {스크립트 파일명} {웹포트번호} 형식으로 실행하면 된다.

locust 서버가 기동되고, 부하를 줄 준비가 된 상태가 된다. 실제 부하 생성은 locust 웹콘솔에 접속해서 해야 한다.  아래 스크립트는 8080트로 locust 웹 클라이언트를 기동하도록 한 명령이다. 

%locust -f ./locustfile.py --port 8080


http://localhost:8080 으로 접속하면 아래와 같이 웹 콘솔이 나온다. 


첫번째 인자는, 몇개의 클라이언트를 사용할것인지 (메뚜기를 몇마리 만들것인지)를 정의하고, 두번째는 Hatch rate 라고 해서, 초마다 늘어나는 클라이언트 수 이다. 처음에는 클라이언트가 1대로 시작되서, 위의 설정의 경우 초마다 하나씩 최대 30개까지 늘어난다.

그리고 마지막은 테스트할 웹 사이트 주소를 입력한다.


Start Swarming 버튼을 누르게 되면 부하테스트가 시작되서 아래와 같이 부하 테스트 화면이 실시간으로 출력된다. 



몇 위의 화면은 Total Request Per Second로, 초당 처리량이고, 두번째는 응답 시간, 그리고 마지막은 현재 클라이언트 수 를 모니터링 해준다. 


No Web

위의 부하테스트는 웹 콘솔을 열어서, 웹 사이트로 부하를 주는 시나리오인데, 웹 부하 테스트가 아니라, 데이타베이스의 부하테스트와 같은 경우에는 웹 사이트의 주소를 정의할 수 없다. 이런 경우에는 CLI로 실행이 가능한데, --no-web 옵션을 주면 된다.

%locust -f ./nested-locust.py --no-web -c 2 -r 1 


위의 명령은 nested-locust.py 파일을 실행하되, --no-web으로 웹으로 부하를 주지 않고, -c 2 로 2개의 클라인트에 대해서 -r 1 1초마다 클라이언트를 (2까지) 하나씩 올리는 부하 테스트 시나리오이다. 웹 UI는 테스트 상태를 모니터링 하고 사람이 쓰기 좋지만 만약에 테스트 과정을 CI/CD 파이프라인내에 넣거나 또는 자동화하고 싶을때는 이렇게 CLI 를 이용할 수 있다. 

코딩 방법

사용방법을 이해하였으면, 이제 스크립트 작성 방법을 살펴보자.

스크립트는 Locust (클라이언트/메뚜기)를 정의해서, Locust의 행동 시나리오 (TaskSet)를 정의해야 한다. 

Locust

지원되는 Locust 종류는 HttpLocust와, Locust 두가지가 있다. HttpLocust는 웹 부하테스트용 클라이언트이고 범용으로 사용할 수 있는 클라이언트 (예를 들어 데이타 베이스 테스트)용으로 Locust라는 클래스를 제공한다. 하나의 부하테스트에서는 한 타입의 클라이언트만이 아니라 여러 타입의 클라이언트를 동시에 만들어서 실행할 수 있다.

예를 들어서 하나의 부하 테스트에서 안드로이드용 클라이언트와, iOS용 클라이언트를 동시에 정의해서 부하 비율을 정의해서 테스트(안드로이드,iOS = 7:3으로) 하는 것이 가능하다.

TaskSet

클라이언트(메뚜기)를 정의했으면, 이 클라이언트가 어떻게 부하를 줄지 시나리오를 정의해야 하는데, 이를 TaskSet이라고 한다. 

TaskSet는 Task의 집합으로 예를 들어, 테스트 시나리오에 리스트 페이지 보기, 상품 선택하기, 댓글 달기 등의 Task등을 정의할 수 있다.

Task

Task는 @task 라는 어노테이션으로 정의하면, TaskSet이 실행될때, TaskSet안에 있는 task를 random 하게 선택해서 실행한다. 아래 예제를 보자


class MyTaskSet(TaskSet):

    wait_time = between(5, 15)


    @task(3)

    def task1(self):

        pass


    @task(6)

    def task2(self):

        pass


위의 예제는 task1과, task2 를 랜덤하게 선택해서 실행하게 하는데, 괄호안의 숫자는 가중치가 된다. 즉 위의 TaskSet은 task1과 task2를 3:6의 비율로 실행하도록 한다.

wait_time

Task를 실행한 다음에 다른 Task를 수행할때 까지 delay 타임을 줄 수 있는데, TaskSet 클래스에 wait_time이라는 attribute로 정의되어 있다. 여기에 값을 지정해주면, 그 시간만 기다렸다가 다음 task를 수행한다. 위의 예제에서는 between(5,15)를 이용해서 5~15초 사이 시간 만큼 랜덤하게 기다렸다가 다음 task를 실행하도록 하였다. 

TaskSequence  & seq_task

TaskSet의 다른 종류로 TaskSequence 라는 클래스가 있는데, TaskSet이 task를 랜덤하게 실행한다고 하면, TaskSequence는 Task를 순차적으로 실행한다. 웹 테스트를 보면, 웹사이트에서 로그인하고, 초기 화면을 들어가고, 상품 목록 페이지로 이동하고 다음 상품 상세 페이지를 보는 것과 같은 순차적인 테스트가 필요할 수 있는데, 이를 지원하기 위한것이 TaskSequence 이다.

TaskSet과 다르게, TaskSequence내에서 순차적으로 실행해야하는 task는 seq_task()로 정의하고, 괄호 안에는 그 순서를 정의한다. 

아래 예제를 보자


class MyTaskSequence(TaskSequence):

    @seq_task(1)

    def first_task(self):

        pass


    @seq_task(2)

    def second_task(self):

        pass


    @seq_task(3)

    @task(10)

    def third_task(self):

        pass


위의 예제는 first_task를 실행하고, 두번째는 second_task를 실행한 후에, 세번째는 third_task가 실행되는데, @task(10) 으로 지정되어 있기 때문에, 10번이 실행된다. 

Nesting

Locust에서 TaskSet은 다른 TaskSet을 호출(Nest) 하는 것이 가능하다. 

예를 들어 부하 테스트 시나리오에서, 게시판에 글을 쓰는 시나리오와 상품을 구입하는 시나리오가 하나의 클라이언트에서 동시에 일어난다고 했늘때, 게시판에 글을 쓰는 TaskSet과, 상품을 구입하는 TaskSet을 정의하고 전체 시나리오에서 이 두 TaskSet을 호출하는 것과 같이 반복적이고 재사용적인 시나리오를 처리하는데 사용할 수 있다. 

아래 예제를 보자, 아래 예제는 UserBahavior TaskSet에서 Nested 라는 TaskSet을 호출하는 예제이다. 


from locust import Locust,TaskSet,task,between


class Nested(TaskSet):

        @task(1)

        def task1(self):

                print("task1")


        @task(1)

        def task2(self):

                print("task2")


        @task(1)

        def stop(self):

                print("stop")

                self.interrupt()



class UserBehavior(TaskSet):

        tasks = {Nested:2}


        @task

        def index(self):

                print("user behavior task")



UserBehavior에는 index라는 Task와 Nested TaskSet이 정의되어 있고, Nested TaskSet은 가중치가 2로 되어 있기 때문에, index task에 비해서 2배 많이 호출된다.

Nested TaskSet은 task1,task2,stop을 가지고 있느넫, 각각 가중치가 1로 이 셋중 하나가 랜덤으로 실행되는데, Nested TaskSet이 실행이 시작되면, 계속 Nested TaskSet 안의 task들만 실행이 되고, 그 상위 TaskSet인 UserBehavior가 원칙적으로는 다시 호출되지 않는다. 즉 Nested TaskSet의 task들로 루프를 도는데, 그래서 stop task에 self.intrrupt를 호출해서, Nested TaskSet 호출을 멈추고, UserBehavior TaskSet으로 리턴하도록 하였다. 

Hook

TaskSet은 실행 전후에, Hook을 정의할 수 있다.

setup & teardown

setup과, teardown은 TaskSet이 생성되었을때와 끝나기전에 각각 한번씩만 수행된다. 

locust가 실행되면, TaskSet 별로 정의된 setup 메서드가 실행되고, 프로그램을 종료를 하면, 종료하기전에 teardown 메서드가 실행된다. 

setup과 teardown은 테스트를 위한 준비와 클린업등에 사용할 수 있는데, 예를 들어 테스트를 위한 데이타 베이스 초기화 등에 사용할 수 있다. 주의 할점은 클라이언트를 여러개 만든다고 하더라도, TaskSet의 setup과 teardown은 각각 단 한번씩만 실행이 된다.

on_start,on_stop

setup과 teardown이 전체 클라이언트에서 한번만 실행된다면, 클라이언트마다 실행되는 Hook은 on_start와  on_stop이 된다. 

on_start는 클라이언트가 생성될때 마다 한번씩 실행된다. 


from locust import Locust,TaskSet,task,between


class UserBehavior(TaskSet):

def setup(self):

print("nested SETUP");


def teardown(self):

print("nested TEAR DOWN");


def on_start(self):

print("nested on start");


def on_stop(self):

print("nested on stop");


@task(1)

def task1(self):

print("task1")


@task(1)

def task2(self):

print("task2")



class User(Locust):

task_set = UserBehavior

wait_time = between(1,2)


코드를 

%locust -f ./locust.py --no-web -c 2 -r 1 

명령으로 2개의 클라이언트를 실행하게 되면 on_start는 단 두번 실행이 된다.

Locust setup & teardown

TaskSet의 Hook에 대해서 알아봤는데,  테스트 클라이언트인 Locust class도, setup과 teardown hook을 가지고 있다. 하나의 Locust 클라이언트는 여러개의 TaskSet을 가지고 있기 때문에, TaskSet마다 정의된 setup과 teardown이 실행되지만, Locust는 하나만 존재하기 때문에, 전역적으로 setup과  teardown은 정확하게 단 한번만 수행이 된다. 

Locust와 TaskSet의 실행 순서를 보면 다음과 같다. 


실행 순서

  • Locust setup

  • TaskSet setup

  • TaskSet on_start

  • TaskSet tasks…

  • TaskSet on_stop

  • TaskSet teardown

  • Locust teardown




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

댓글을 달아 주세요

API 게이트 웨이

What is API 게이트웨이

API 게이트웨이는 API 클라이언트와 서비스 사이에 프록시 처럼 위치하여, 클라이언트로 부터 온, API 요청을 받아서, 서비스로 라우팅 하는 역할을 한다. 

각각의 서비스에서 구현해야 하는 기능을 API 게이트웨이단으로 통합함으로써, 서비스 개발을 간편하게 할 수 있고, 메세지 포맷 변경이나, 플로우 컨트롤과 같은 고급 기능을 사용하여, 기존 API 호출에 추가적인 기능을 더할 수 있는 유연성을 더할 수 있게 된다.


여러가지 기능이 있겠지만, 크게 아래와 같이 5가지로 나눠볼 수 있다.

  • 인증

  • 모니터링,로깅

  • 플로우 컨트롤

  • 메시지 변경

  • 오케스트레이션(매쉬업)





<출처 : 마이크로소프트 애저 홈페이지>


인증

API를 호출할때, API 호출에 대한 인증을 수행한다. 서버간의 API 통신일 경우에는 간단하게 API 키를 사용하는 방법등이 있고, 다수의 클라이언트 (모바일 단말, 웹 자바스크립트 클라이언트)의 경우에는 사용자 계정 인증 후에, JWT 토큰을 발급하는 방식이 있다. 

모니터링 & 로깅

API 호출에 대한 모니터링을 통해서 API 별 응답시간, 장애율,호출 횟수를 모니터링 할 수 있게 하고, 경우에 따라서 개별 API 호출을 로깅한다. 

Metering & Charging

API를 대외로 서비스 하는 경우, API의 호출량을 모니터링 해야 하는데, 이 양을 기반으로 해서 API 서비스 호출 횟수를 통제하거나 또는 유료 API의 경우에는 과금을 해야 하기 때문에, 대외로 서비스를 하는 오픈 API 플랫폼인 경우에는 이러한 기능이 필요하다. 

메시지 플로우 컨트롤

메시지 플로우 컨트롤은 클라이언트로 부터 들어온 메시지의 흐름을 바꾸는 방법인데, 예를 들어 같은 API라도 버전에 따라서 구버전과 새버전으로 트래픽을 9:1로 라우팅하는 카날리 배포 방식이라던지, 들어온 API를 클라이언트의 위치에 따라 미국이나,한국에 있는 서비스로 라우팅을 하는 등의 로직을 구현할 수 있다. 

Limiting (throttling)

조금 더 고급화된 구현방법으로는 Limiting 기법이 있는데, 특정 API에 대해서 정해진 양 이상의 트래픽을 받지 못하도록 하여, 서비스를 보호하거나 또는 유료 API 서비스인 경우 QoS (Quality Of Service)를 보장하기 위해서 특정 양까지만 트래픽을 허용하도록 컨트롤할 수 있다. 

메시지 변환

메시지 변환은 고급 기능 중의 하나인데, 헤더에 있는 내용을 메시지 바디로 붙이거나, 다른 API를 호출해서 응답메시지를 두 API를 합해서 응답을 해주는 기능등을 구현할 수 있다. 

프로토콜 변환

그외에도 서로 다른 프로토콜에 대한 변환이 가능한데, 예를 들어 클라이언트로 부터는 gRPC로 호출을 받고, API서버로는 REST로 호출한다던가. SOAP/XML 기반의 메시지를 REST로 변환하는 등의 기능 구현이 가능하다. 

호출 패턴 변환

API 서버가 큐를 기반으로한 비동기 메시지 처리 패턴일때, API 게이트 웨이에서 이를 동기화 처리 패턴으로 바꿔서, 클라이언트 입장에서는 동기로 호출을 하면, API 서버에서는 비동기로 호출하는 형태와 같이 호출 패턴을 변화시킬 수 있다. 

오케스트레이션(매시업)

API 클라이언트가 한번 호출을 할때, 동시에 여러개의 API를 호출하도록 API 게이트웨이에서 호출을 해주는 방식이다. 예를 들어 API 클라이언트가 “상품 구매" 라는 API를 호출 하였을때, API 게이트웨이가 “상품 구매" API 서비스를 호출하고, “상품 추천" 이라는 API도 호출해서, 클라이언트로 구매 완료 메시지와 함께 추천 상품 리스트를 한번에 리턴하는 방식이다. 


경량형 게이트웨이와 중량형 게이트웨이

API 게이트 웨이는 근래에 마이크로 서비스가 각광을 받으면서 언급되기 시작하고 있지만, 사실 API 게이트 웨이라는 기술 자체는 오래된 기술이다. API 게이트 웨이 이전에는 SOA 제품군의 일부로 ESB (Enterprise Service Bus)가 SOAP/XML 웹서비스 기반으로 이러한 역할을 해주었는데, 잘못된 구현 패턴을 사용해서 문제가 된 케이스가 많다. 예를 들어서 과도하게 오케스트레이션 기능을 사용하였을 경우 API 게이트웨이 자체에 부하가 걸려서 전체 API 서비스에 성능 문제를 야기 하거나, 또는 파일 업로드 요청을 API 게이트 웨이를 통해서 함으로써 게이트웨이의 부하때문에 성능 문제가 생기는 경우가 있었다. 

SOAP/XML에서 REST API로 오면서, API 게이트웨이도 이러한 문제를 피하는 패턴형식으로 API 게이트 웨이가 포지셔닝이 달라졌다. 사실 매시업이나, 메시지 라우팅, 메시지 변환등과 같은 기능들은 사실 사용되는 일이 그렇게 많지 않고, 클라이언트단에서 처리해도 충분하기 때문에, 이러한 고급 기능 보다는 API 인증, 로깅/모니터링 위주의 최소한의 기능만 가지면서 경량화된 형태로 배포 및 운영이 간편한 경량형 API 게이트 웨이들이 소개 되기 시작하였고, 반대로 오픈 API를 고객을 대상으로 제공하면서 메시지 변환이나, 과금등 고급 기능을 제공하는 중량형 API 게이트 웨이들도 꾸준히 제품들이 제공되고 있다.


이러한 제품들에 대한 명시적인 구분은 없지만, 필자의 기준으로 봤을때, 경량형 API 게이트 웨이는 Kong등을 들 수 있고, 중량형 API 게이트 웨이는 APIgee, MuleSoft 등을 들 수 있다. 


전통적으로 중량형 API 게이트웨이의 아키텍쳐 구조는 API 게이트웨이가 클러스터 형태로 배포 된 후에, 그 뒤에 API 서버들이 배포되는 형태라면, 경량형 API 게이트웨이는 nginx나 node.js와 같은 가벼운 프록시 서버로 배포하고, 용량이 필요하다면 개별의 인스턴스들을 스케일링으로 늘리는 방법을 사용한다. 근래에는 중량형 API 게이트 웨이도 이러한 구조를 많이 참고해서 구조를 개선하고 있다. 

쿠버네티스와 API 게이트 웨이

이러한 API 게이트웨이들이 쿠버네티스의 등장과 함께 주목 받으면서, 쿠버네티스 지원을 넓혀나가고 있는데, 이 과정에서 흥미로운 아키텍쳐 구성을 보인다. 쿠버네티스는 이미 Service라는 리소스를 통해서 마이크로 서비스를 정의할 수 있고, Service를 묶어서 로드밸런싱을 하는 Ingress라는 리소스를 가지고 있다. 즉 Ingress는 Service간의 라우팅을 할 수 있는 일종의 초경량 API 게이트웨이로써의 기능을 가지는 구조인데, API 게이트웨이의 쿠버네티스 연동은, 새롭게 애플리케이션을 배포하고 설정하는 방식이 아니라 쿠버네티스 리소스 자체에 API 게이트웨이를 녹이는 방식으로 접근하고 있다.


아래 Kong API 게이트웨이의 구조를 보면, Kong 전용으로 컨테이너를 배포하지 않고, Kong API 게이트웨이용 Ingress를 정의해서 사용자가 Ingress를 사용하게 되면 API 게이트웨이를 사용할 수 있게 하였고, 메시지 변환이나 기타 기능이 필요할 경우 이를 CRD(Custom Resource Definition) 로 미리 등록해놔서 이를 사용하도록 하고 있다. 



<Kong API 게이트 웨이 아키텍처 출처  > 


구글의 Apigee의 경우에는 Apigee Hybrid라는 이름으로 Kubernetes를 지원하는데, 구조는 Istio Ingress를 앞단에 둬서 라우터로 이용하고 API 게이트웨이에서 처리하는 각종 로직을 Ingress 뒤에 Pod(아래 그림 4번)로 배포하여 기능을 수행하도록 하고 있다. 

API 호출을 통과 시키는 프록시는 이렇게 단순화 시키고, 관리 콘솔은 구글 클라우드로 위치 시켜서 운영을 최대한 단순화 시켰다. 


<출처 : 구글 apigee hybrid 문서 > 

구글의 API 게이트 웨이 - Cloud Endpoint

구글의 Cloud Endpoint는 경량형 API이다. 

기본적으로 nginX로 배포되서 프록시 처럼 동작하며, 기능은 API 인증과 모니터링/로깅정도 밖에 없지만 구조와 기능이 단순하기 때문에 사용이 편리하며, 또한 nginX 기반의 배포 방식으로 인해서 구글 클라우드 뿐만 아니라, 다른 클라우드나 ON-PREM에도 사용이 가능하다는 장점이 있다. 


구조는 다음과 같이 되어 있다.



<그림. Cloud Endpoint ESP 아키텍쳐 / 출처 >


ESP는 Cloud Endpoint의 Proxy, 즉 nginX를 이야기 한다. 프록시는 실제로 쿠버네티스 클러스터에 배포되고, 모니터링과 로깅, 컨트롤러는 구글 클라우드의 콘솔을 이용하게 된다. 

쿠버네티스에 배포하는 형태는 다음 그림과 같다. 

애플리케이션 컨테이너와 함께, 같은 Pod로 패키징 되고, ESP에서는 애플리케이션으로 트래픽을 localhost로 포워딩한다. 


<그림. ESP의 쿠버네티스 배포 형태 >


이런 구조를 가지기 때문에, Ingress, Istio 등 다른 쿠버네티스 플랫폼들과 유연하게 통합이 된다. 

API 인증은 여러가지 방법을 지원하는데, Firebase authentication, Google ID,SAML(SSO) 방식으로 사용자를 인증한후에, API 키(JWT)를 발급하는 방법이나, 서버간일 경우 Service Account를 사용하는 방법 또는 단순하게 API KEY를 사용하는 방법이 있고 또는 Custom 인증을 구현하여 JWT 토큰으로 인증할 수 있다. 인증 방식은 이 문서를 참고하면 된다.

로깅/모니터링

로깅과 모니터링은 End Point 콘솔에서 볼 수 있으며, 아래와 같이 서비스 단위로 응답 시간과 에러율등을 출력해준다


OPEN API (Swagger) 기반 포탈

그리고 중요한 기능중의 하나인데, API 스펙 문서를 포탈 형태로 보여준다. Swagger 문서 형태로 작성을 하면 아래와 같이 독립된 웹사이트 형태로 API 스펙 문서를 보여준다.


<그림. Cloud endpoint developer portal / 출처


멀티/하이브리드 클라우드

마지막으로, Cloud End Point는 ngnix 기반으로 배포가 가능하기 때문에, 구글 클라우드 뿐 아니라 타 클라우드나 온프렘 데이타 센터내에도 프록시(ESP)를 배포해서 사용하는 것이 가능하다. 


참고 문서


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

댓글을 달아 주세요

오픈소스 API 게이트웨이 Kong

#2 아키텍쳐와 간단한 테스트

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

Kong 아키텍쳐

Kong API 서버의 배포 아키텍쳐는 다음과 같다. 


출처 : stackoverflow.com

Kong API 게이트웨이 각각 nginx 로 되어 있고, DB 모드의 경우에는 별도의 데이타 베이스 인스턴스를 사용한다. 지원되는 데이타 베이스로는 카산드라나 postgres를 사용할 수 있다. 

데이타 베이스를 사용하더라도 변경된 설정은 인스턴스에 바로 반영되지 않는다. 각 인스턴스가 설정 내용을 캐슁하기 때문인데, 캐쉬가 리프레쉬되어야 설정 내용이 반영된다. 

클러스터에서 Kong API 게이트웨이 인스턴스간의 로드 밸런싱을 하기 위해서는 각 인스턴스 앞에 로드밸런서를 배치 시킨다. 만약에 Session Affinity가 필요하다면 L4에서 IP나 Session based affinity를 설정해야 한다. 


Kong API 게이트웨이에서 각종 기능들은 nginX의 Lua 스크립트 모듈은 OpenResty를 이용한다. 

간단한 API 테스트

개념 이해가 끝났으면 간단한 테스트를 해보자

파이썬으로 간단한 API 서버를 구현하였다. app.py로 simpleapi:9001/api URL로 HTTP GET으로 간단하게 서빙하는 API가 있다고 할때


kong.yml을 다음과 같이 구현한다. 

_format_version: "1.1"


services:

- name: simpleapi

  url: http://simpleapi:9001

  routes:

  - name: simpleapi-route

    paths:

    - /simpleapiservice



simpleapi라는 이름으로 서비스를 등록하고 API 서버의 호스트 경로는 http://simpleapi:9001로 등록한다.

위의 설정은 {Kong IP}/simpleapiservice URL을 —> http://simpleapi:9001로 라우팅하도록 한 설정이다.

%curl localhost:8000/simpleapiservice/api


실행하면 http://simpleapi:9001/api 를 호출한 결과가 리턴된다. 


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

댓글을 달아 주세요

Kong API gateway #1 - 설치와 둘러보기

카테고리 없음 | 2019. 11. 10. 23:58 | Posted by 조대협

오픈소스 API 게이트웨이 Kong

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


Kong API 게이트 웨이를 설치 하는 방법은 여러가지가 있지만, 여기서는 테스트 환경용으로 로컬 환경에 도커 이미지로 간단하게 설치 하는 방법에 대해서 알아본다. 

(도커 기반 Kong API 게이트 웨이 설치 방법 참고 :  https://docs.konghq.com/install/docker/)

전체 설치 방법은 https://docs.konghq.com/install 를 참고하기 바란다. 

설치 하기

Kong 설치하기

Kong API 게이트웨이는 데이타베이스가 필요하지만, 간편하게 데이타 베이스가 없는 dbless 모드로 설치를 한다. 웹 인터페이스를 이용하기 위해서 Konga (https://pantsel.github.io/konga/) 를 추가로 설치할 것인다. Konga도 도커 컨테이너로 설치한다. 이때 Konga도 가 Kong API 게이트웨이로 통신을 해야 하기 때문에,둘을 같은 네트워크로 묶어서 서로 통신하도록 하겠다.

(참고 : 도커 컨테이너는 네트워크 공간이 각각 분리되기 때문에, 다른 컨테이너간에 통신을 하기 위해서는 네트워크 공간을 묶어줄 필요가 있다. )

네트워크 생성하기

Kong API 게이트 웨이 Admin API를  Konga에서 호출할 수 있도록 같은 네트워크로 묶기 위해서 “kong-net”이라는 네트워크를 생성한다

$ docker network create kong-net

디스크 볼륨 생성하기

여기서는 데이타베이스 없이 Kong API 게이트웨이를 설치하기 때문에, 디스크 볼륨을 정의하여 디스크에 설정 정보를 저장하도록 한다. 아래와 같이 디스크 볼륨을 생성한다.  

$ docker volume create kong-vol

설정 파일 작성하기 

디폴트 설정 파일이 있어야 게이트웨이가 기동이 되기 때문에 디폴트 설정 파일을 만든다.

설정 파일은 앞서 만든 kong-vol 볼륨에 kong.yml 로 저장이 된다. 파일을 생성하기 위해서 볼륨의 호스트 파일상의 경로를 알아보기 위해서 docker volume inspect 명령을 사용한다. 


$ docker volume inspect kong-vol


[

     {

         "CreatedAt": "2019-05-28T12:40:09Z",

         "Driver": "local",

         "Labels": {},

         "Mountpoint": "/var/lib/docker/volumes/kong-vol/_data",

         "Name": "kong-vol",

         "Options": {},

         "Scope": "local"

     }

 ]


디렉토리가 /var/lib/docker/volumes/kong-vol/_data임을 확인할 수 있다. 호스트(로컬 PC)의 이 디렉토리에서 아래와 같이 kong.yml 파일을 생성한다. 

kong.yml 파일 (주의 kong.yaml이 아님)

_format_version: "1.1"


services:

- name: my-service

  url: https://example.com

  plugins:

  - name: key-auth

  routes:

  - name: my-route

    paths:

    - /


consumers:

- username: my-user

  keyauth_credentials:

  - key: my-key


생성이 끝났으면 아래 도커 명령어를 이용해서 Kong API 게이트웨이를 기동 시킨다.

% docker run -d --name kong \

     --network=kong-net \

     -v "kong-vol:/usr/local/kong/declarative" \

     -e "KONG_DATABASE=off" \

     -e "KONG_DECLARATIVE_CONFIG=/usr/local/kong/declarative/kong.yml" \

     -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \

     -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \

     -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \

     -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \

     -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \

     -p 8000:8000 \

     -p 8443:8443 \

     -p 8001:8001 \

     -p 8444:8444 \

     kong:latest


설치가 제대로 되었는지

%curl http://localhost:8001 

명령을 이용하여 확인한다. 

Konga설치 하기

다음은 웹 UI를 위해서 Konga를 설치한다. https://pantsel.github.io/konga/

여러가지 설치 방법이 있지만 간단하게 하기 위해서 역시 도커로 설치한다. 

% docker run -d -p 1337:1337 --network=kong-net --name konga -v /var/data/kongadata:/app/kongadata -e "NODE_ENV=production"              pantsel/konga


이때, Kong API 서버 8001 포트로 통신을 해서 관리 API를 호출해야 하기 때문에, network를 "kong-net”으로 설정해서 실행한다. 설치가 끝나면 http://localhost:1337을 접속하면 Konga UI가 나오고, 관리자 계정과 비밀 번호를 입력할 수 있다.


로그인을 하면 Kong API 게이트웨이와 연결을 해야 하는데, 이름(임의로 적어도 된다.) Kong API 게이트웨이 Admin URL을 적어야 한다. 이때 http://kong:8001 을 적는다. (kong은 Kong API 게이트 웨이를 도커 컨테이너로 기동할때 사용한 컨테이너 이름이다. localhost로 하면 Konga 컨테이너의 로컬 호스트를 찾기 때문에 정상적으로 작동하지 않는다. )


이제 설정할 준비가 되었다. 


참고 : kong-dashboard 도 테스트 해봤지만, UI가 Konga가 훨씬 나은듯 해서, 글 내용을 변경함

% docker run --network=kong-net -itd --rm -p 8080:8080 pgbi/kong-dashboard start --kong-url http://kong:8001 --basic-auth {사용자이름}={비밀번호}


쿠버네티스와 Kong 통합 둘러보기

Kong API 게이트웨이를 사용하는 방법은 VM 기반의 일반 서버 앞에 설치해서 사용해도 되지만 쿠버네티스에 같이 사용할 경우 쿠버네티스 기반의 마이크로 서비스 아키텍쳐 패턴 구현에 더 용이 한다. 생각보다 잘 되어 있어서 놀랐다. 

설치 방법은 https://docs.konghq.com/install/kubernetes/ 에 설명되어 있는데, Helm이나 Kustomize로 설치가 가능하고, Kong은 CRD 형태로 지정된 Ingress로 프로비저닝이 된다. 

그리고 설정은 별도의 데이타 베이스 설치가 필요 없고 쿠버네티스의 컨트롤 플레인(마스터)에 저장되기 때문에 운영이 비교적 간단하다.


데이타베이스가 없을 경우, API 메트릭 저장하는 부분에 대해서 우려를 했는데, 아래와 같이 외장 플러그인을 지원한다. 




데이타 독 통합이 지원되고, 프로메테우스를 통하여 메트릭 저장이 가능하다. 그리고 Kong 용 프로메테우스/그라파나 대쉬보드도 훌륭하게 지원되고 있고, 분산 트렌젝션 추적을 위한 ZipKin도 이미 지원하고 있다. 


반면 로깅이 약간 애매한데, ELK(EFK)지원은 직접적으로는 보이지는 않는다. 



대신 파일 로그가 있기 때문에, 이 파일을 읽을 수 있는 Fluentd를 설치하면 될것으로 보이지만, 아무래도 컨테이너를 새로 말아야 하는 부담이 있기도 하고, 이를 통해서 메트릭이나 API 전용 로깅을 하는데는 별도의 설정이 필요할듯 하다. 


일단 GKE (구글 클라우드)의 경우에는 마켓 플레이스를 통해서 쉽게 설치가 가능하니 한번 테스트해볼만한 가치는 있을듯


11월 10일 업데이트

DBless 모드를 사용하게 되면, 동적으로 설정 변경이 불가능하다. Konga를 설치하더라도 설정 내용을 볼수만 있고 설정 변경은 불가능하다. 그외에도 지원되는 플러그인등에 대한 제약 사항이 있기 때문에 이 부분을 주의해야 한다.

제약 사항


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

댓글을 달아 주세요

도커 볼륨

클라우드 컴퓨팅 & NoSQL/도커 & 쿠버네티스 | 2019. 11. 10. 21:38 | Posted by 조대협

도커 볼륨에 대한 메모


도커 컨테이너에서 파일을 저장하게 되면 디폴트로는 도커 컨테이너 Writable 레이어에 저장된다. 컨테이너 레이어에 저장이 되기 때문에, 영속적이지 못하고 컨테이너가 내려가게 되면 지워지는 임시 저장소 이다. 


컨테이너의 디스크를 컨테이너가 내려가더라도 영속적으로 저장하고 관리할 수 있는데, 이를 볼륨(Volume)이라고 한다.


%docker create volume {볼륨 이름} 

명령어를 이용해서 생성이 가능하다.

생성된 볼륨들은


% docker volume ls 

명령으로 확인이 가능하다

볼륨에 대한 상세 설정을 보려면 


%docker volume inspect {볼륨 이름}

을 사용하면 된다. 

docker volume inspect myvol

[

    {

        "CreatedAt": "2019-11-10T21:28:26+09:00",

        "Driver": "local",

        "Labels": {},

        "Mountpoint": "/var/lib/docker/volumes/myvol/_data",

        "Name": "myvol",

        "Options": {},

        "Scope": "local"

    }

]


위의 예제를 보면 생성된 볼륨의 내용을 볼 수 있는데, 로컬(호스트머신)의 어느 디렉토리에 볼륨이 저장되는지 확인할 수 있다. 위의 예제에서는 /var/lib/docker/volumes/myvol/_data 에 저장이 된다.
생성된 볼륨을 컨테이너의 파일 시스템에 마운트 시키는 방법은 다음과 같다.
$ docker service create \
     --mount 'type=volume,src=<VOLUME-NAME>,dst=<CONTAINER-PATH>,volume-driver=local,volume-opt=type=nfs,volume-opt=device=<nfs-server>:<nfs-path>,"volume-opt=o=addr=<nfs-address>,vers=4,soft,timeo=180,bg,tcp,rw"'
    --name myservice \
    <IMAGE>

src에 생성한 볼륨 이름을 dst에는 이 볼륨을 마운트 시킬 컨테이너 내부의 디렉토리를 지정하면 된다. 

Bind Mount
볼륨의 경우에는 도커 컨테이너에서만 그 파일을 내용을 볼 수 있고, 다른 컨테이너나 또는 호스트에서는 그 파일(디렉토리)의 내용을 볼 수 없다. 호스트나 다른 컨테이너에서도 접근할 수 있는 볼륨이 있는데, 이를 Bind mount라고 한다.


<그림. 도커 컨테이너 디스크 볼륨 종류와 개념 >


볼륨 생성 방법 자체는 동일하나 도커 컨테이너에 마운트 할때 Type을 "type=bind" 라고 



$ docker run -d \
  -it \
  --name devtest \
  --mount type=bind,source="$(pwd)"/target,target=/app,readonly \
  nginx:latest


tmpfs


볼륨 종류중에는 tmpfs라는 타입이 있는데, 일반 볼륨과는 다르게 호스트의 디스크 공간을 사용하는 것이 아니라 내용을 메모리에 저장하는 일종의 램 디스크라고 보면 된다.


storagedriver

마지막으로, 호스트의 디스크 이외의 다른 디스크, 예를 들어 NFS, iSCSI 등을 사용할 수 있는데, 이는 이 디스크 타입을 접근하기 위한 storagedriver를 사용해야 한다. 



참고 자료 & 이미지 예제 출처 : https://docs.docker.com/storage/https://docs.docker.com/storage/bind-mounts/

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

댓글을 달아 주세요

KNative 를 보던중에, Autoscaling 처리를 어떻게 하는지 확인해보니,

기본은 Knative instance (한 Pod 겠지) 당 처리할 수 있는 concurrent request 수를 정해놓고, 이를 넘을 경우에 auto scaling 하는 방식이다. CPU가 아니라 connection 수를 통해서 한다. 

containerConcurrency limits the amount of concurrent requests are allowed into the application at a given time (hard limit), and is configured in the revision template.

스케일링 설정은


spec:
  template:
    metadata:
      autoscaling.knative.dev/minScale: "2"
      autoscaling.knative.dev/maxScale: "10"

min, max 값을 지정하는데, min 이 0 이면 콜드 스타트가 발생할 수 있기 때문에, 0 보다는 큰 수를 주는 것이 좋다. 


디폴트가 이 concurrent connection 수를 이용하는 방식인데, CPU 베이스도 가능하다.

spec:
  template:
    metadata:
      autoscaling.knative.dev/metric: concurrency
      autoscaling.knative.dev/class: hpa.autoscaling.knative.dev

위의 metric 모드를 사용하게 되면, concurrency 모드를 사용하게 되고, class 모드를 사용하게되면, HPA (Horizontal Pod Autoscaler)를 사용하게 된다. 


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

댓글을 달아 주세요

효율적인 도커 이미지 만들기

#2 도커 레이어 캐슁을 통한 빌드/배포 속도 높이기

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


도커는 이미지 단위로 빌드를 하고 배포를 하지만, 도커의 이미지는 여러개의 레이어로 되어 있다. 아래와 같은 자바 애플리케이셔을 패키징한 도커 컨테이너 이미지가 있다고 하자

FROM openjdk:8-jre-alpine

ARG ./target/hellospring-0.0.1-SNAPSHOT.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]


이 이미지가 어떤 레이어로 구성되어 있는지를 보려면 %docker history {컨테이너 이미지명} 을 실행하면 이미지의 레이어를 볼 수 있는데,   각각의 명령항에 따라서 레이어가 생성된것을 볼 수 있다. 



그리고 각각의 크기를 확인할 수 있다. 위의 history를 보면, 




  • 맨 아래 5.53M가 알파인 리눅스의 베이스 이미지

  • 그리고 위에서 4번째 79.4M가 JRE 설치

  • 위에서 2번째가 33.8M로, 애플리케이션 파일 Jar을 복사한 내용

으로 확인할 수 있다. 계층은 여러개이지만, 주로 용량을 많이 사용하는 이미지는 이 3개의 계층임을 확인할 수 있다.


이 컨테이너에서 애플리케이션을 다시 컴파일 하고 패키징해서 push를 해보면 아래와 같이 33.82M에 해당하는 애플리케이션 Jar 만을 복사하고 나머지는 “Layer already exist”와 함께 별도로 레이어를 푸쉬하지 않는 것을 확인할 수 있다. 


도커 컨테이너는 효과적인 배포를 위해서 pull/push 시에, 레이어별로 캐슁을 해서, 변경이 없는 레이어에 대해서는 다시 pull/push를 하지 않는다

자바 애플리케이션을 여러개의 레이어로 나눈 경우

이 레이어별 캐슁 기능을 잘 활용하면, 애플리케이션 컨테이너의 push/pull 시간을 많이 단축시킬 수 있다. 첫번째 push/pull은 전체 이미지를 올려야하기 때문에 시간 차이는 없겠지만, 그 후 애플리케이션만을 업데이트할 경우에는 애플리케이션에 관련된 파일만 실제로 복사되도록하고, 나머지 레이어는 캐슁된 이미지를 사용하도록 하는 방법이다.


Springboot와 같은 자바 애플리케이션은 애플리케이션 파일은 /classes에 저장되지만, 나머지 참조하는 여러 jar 파일들이 있다. 이러한 jar 파일들은 변경이 없기 때문에, 다시 재배포할 필요가 없기 때문에 캐슁을 하도록 하는 것이 좋다.


그러나 앞의 예제에서는 이 모든 라이브러리 jar 파일들을 하나의 app.jar 에 묶어서 복사하는 구조이기 때문에, 라이브러리 jar 파일만을 별도로 캐슁할 수 없다. 

그래서, 아래와 같이 애플리케이션 jar 파일을 푼 다음에, 각각의 디렉토리를 별도로 복사하는 방법을 사용한다. mvn package 에 의해서 생성된 jar 파일을 target/depdency 라는 디렉토리에 풀고, 각 디렉토리를 Dockerfile에서 각각 복사하도록 설정하였다. 


FROM openjdk:8-jre-alpine

ARG DEPENDENCY=target/dependency

COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib

COPY ${DEPENDENCY}/META-INF /app/META-INF

COPY ${DEPENDENCY}/BOOT-INF/classes /app

COPY ${DEPENDENCY}/org /org


ENTRYPOINT ["java","-cp","app:app/classes/*","com.terry.HellospringApplication"]


이렇게 하게 되면, 사용자가 작성한 코드는 /classes 에 컴파일 된 상태로 저장되고, 나머지 라이브러리들은 /BOOT-INF/lib  등의 디렉토리에 저장된다. 이를 각각 Dockerfile에서 COPY 명령으로 복사하게 되면 별도의 레이어로 생성된다. 

아래는 위의 Dockerfile을 이용해서 빌드한 이미지의 레이어이다. 



앞의 예제의 경우 애플리케이션 jar 파일이 33.82M의 단일 레이어로 생성되었지만, 이 컨테이너는 176k,1.34k,1.84k,16.7M  4개의 레이어로 생성된것을 확인할 수 있다. 




이중에서 1.84k,16.7M 레이어는 Springboot 라이브러리이기 (jar파일) 때문에 변경이 없다. 그래서 캐슁이 가능한데, 


아래는 이 컨테이너를 한번 등록해놓고, 그 다음 애플리케이션 코드를 변경해서 새로 빌드하고 푸슁하는 과정이다. 


보는 것과 같이 위의 2 레이어만 새롭게 푸슁되는 것을 확인할 수 있다. 

이렇게 하면, 아래 OS 레이어, JRE, 그리고 Spring boot의 jar 라이브러리들은 모두 캐슁되고, 배포시에는 실제로 애플리케이션 class 파일만 전송되게 되기 때문에 배포 시간을 많이 단축할 수 있다.


이때 주의 할점은 기존 디렉토리에

BOOT-INF/classes

BOOT-INF/lib

META/lib

org


이런 디렉토리들이 있는데, mvn으로 새롭게 jar를 빌드한후 target/dependecy 파일에 전체 파일을 풀어버리게 되면 4개의 디렉토리가 모두 업데이트가 된다. BOOT-INF/lib,META/lib,org 디렉토리의 실제 파일 내용이 변경이 되지 않았다 하더라도, 새로운 파일로 업데이트하였기 때문에, 파일의 해쉬값이 변경되게 되고, Docker 는 빌드시에, 이 디렉토리가 변경이 되었다고 판단하고 기존의 캐슁된 레이어를 재활용하지 않고 새롭게 레이어를 생성해서 push 하기 때문에, 실제 캐쉬 효과를 볼 수 없다. 

그래서 빌드 과정에서 기존에 변경되지 않은 파일과 디렉토리는 건들지 않고, 변경된 파일만 업데이트 하도록 빌드를 구성해야 한다. 


여기서는 자바 애플리케이션을 기준으로 설명하였지만, Javascript나, 이미지와 같은 정적 파일이 있거나 라이브러리나 드라이브 설치등의 중복되는 부분이 많은 애플리케이션의 경우에는 같은 원리로 각각 레이어를 나눠 놓으면, 캐슁을 통한 push/pull 속도 절약의 효과를 볼 수 있다.


예제 파일은 스프링부트 홈페이지를 참고하였습니다. :)

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

댓글을 달아 주세요

효율적인 도커 이미지 만들기

#1 작은 도커 이미지 만들기

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


일반적으로 도커를 이용해서 자바 애플리케이션 컨테이너를 빌드하면 보통 사이즈가 500~700M 로 배우 큰 이미지가 생성된다. 이는 Ubuntu와 같은 일반 OS 이미지 위에, JDK/JRE를 설치하기 때문인데, 실제로 자바 애플리케이션만을 실행할때는 기타 툴들 (ftp,telnet, 기타 시스템 데몬)이 필요하지 않다.


도커 이미지 사이즈는 빌드와 배포 시간에 많은 영향을 주고, 쿠버네티스와 같은 컨테이너 스케쥴링 솔루션의 경우 도커 이미지가 디스크 공간을 잡아 먹기 때문에, 작은 컨테이너가 여러모로 이점이 많다. 

작은 도커 이미지 만들기

alpine linux는 경량화를 추구하면서 보안을 강화한 이미지로 꼭 필요한 라이브러리와 시스템 데몬이 포함되어 있기 때문에 일반적인 OS이미지에 비해서 그 사이즈가 매우작다.



<그림. 이미지 사이즈별 비교> 

Java alpine 이미지

alpine linux를 기본으로 해서, 애플리케이션 구동을 위한 이미지들이 이미 많이 있는데, java의 경우 openjdk 기반의 alpine 이미지가 있다. 이미지 명은 openjdk:<자바 버전> 식으로 되고, 만약 java-8인 경우 openjdk:8 식으로 정의된다. alpine 리눅스용 openjdk 이미지는 openjdk:<자바버전>-alpine 이 된다. 


아래는 openjdk:8과, openjdk:8-alpine 이미지로 만든 컨테이너의 사이즈 비교이다. 간단한 Hello World Spring boot jar 파일 (17M정도)를 포함하였다. 



사이즈는 openjdk:8 이미지가 552M, openjdk:8-alpine은 139M가 나오는 것을 확인할 수 있다.


JDK보다는 JRE

흔히들 하는 실수중의 하나가 자바 런타임용 컨테이너를 만들때, 컴파일러가 포함된 JDK 환경을 사용한다는 것이다. 보통 자바 런타임은 JDK 없이 JRE 만 있어도 충분하다. 아래는 JRE 8 Alpine 리눅스 이미지를 이용해서 만든 컨테이너 이다.  베이스 이미지 명은 openjdk:<자바 버전>-jre-alpine 을 사용한다. 




OpenJDK 8-alpine은 139M, OpenJDK 8 JRE alpine은 119M가 되는 것을 확인할 수 있다. (20M정도 더 적다)

그러면 항상 OpenJDK에 대한 alpine JRE 이미지를 사용할 수 있는가? 답은 아니다. https://hub.docker.com/_/openjdk?tab=tags 에 들어가면 공식적으로 배포되어 있는 이미지를 확인할 수 있는다. JDK 버전에 따라서 alpine 리눅스를 안정적으로 지원하지 않을 경우에는 해당 이미지가 제공되지 않고, 다른 Linux를 사용하기 때문에, 이미지 사이즈가 200M가 넘는다. 이럴 경우에는 대안으로 Docker slim 이미지를 사용하면 된다. 


그러면 이러한 이미지들은 믿을만 한가? 도커 컨테이너 리파지토리인 hub.docker.com에서 openjdk를 찾아보면 다음과 같이 “Docker Official Images” 라는 마크가 나온다. 


이 이미지는 도커에서 관리하는 이미지로, 도커환경에 최적화 되어 있고, 또한 보안 위협에 대한 내용을 스캔해서 안전하게 제거되는 이미지이다. 

“Each of the images in the Official Images is scanned for vulnerabilities. The results of these security scans provide valuable information about which images contain security vulnerabilities, and allow you to choose images that align with your security standards.”

출처 : https://docs.docker.com/docker-hub/official_images/


결론적으로 이야기 하자면, 도커 허브에서 제공되는 Official 이미지를 사용하되, alpine 리눅스 기반으로 되어 있는 이미지를 사용하는 것이 가장 좋다.

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

댓글을 달아 주세요

XGBoost 개념 이해

빅데이타/머신러닝 | 2019. 9. 17. 23:31 | Posted by 조대협

XGBoost 알고리즘의 개념 이해


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


XGBoost는 Gradient Boosting 알고리즘을 분산환경에서도 실행할 수 있도록 구현해놓은 라이브러리이다. Regression, Classification 문제를 모두 지원하며, 성능과 자원 효율이 좋아서, 인기 있게 사용되는 알고리즘이다.


XGBoost는 여러개의 Decision Tree를 조합해서 사용하는 Ensemble 알고리즘이다.

먼저 Decision Tree에 대한 개념을 보면 다음과 같다. 여러개의 이진 노드를 겹쳐서 피쳐별로 판단을 해서 최종 값을 뽑아내는 형태가 된다. 


<그림. Decision tree>


Ensemble은 여러개의 모델을 조합해서 그 결과를 뽑아 내는 방법이다. 정확도가 높은 강한 모델을 하나 사용하는 것보다, 정확도가 낮은 약한 모델을 여러개 조합 하는 방식이 정확도가 높다는 방법에 기반한 방법인데, 


Ensemble은 방식에 따라서 Bagging과 Boosting 으로 분류된다.

Bagging

Bagging은 여러 모델을 사용할때, 각 모델에서 나온 값을 계산하여, 최종 결과값을 내는 방식이다.

예를 들어 아래 그림과 같이 모델 m1,m2,m3 3개의 모델이 있을때, 입력 데이터 X를 모델 m1~3에 넣고, 그 결과값을 받아서 합산 (또는 평균등 여러가지 방법이 있음)해서, 최종 결과를 취하는 방식이다. 

모델 m1~m3 로 데이터를 넣을때는 원본 데이타 x에서 매번 다시 샘플링을 해서 다른 샘플 데이터를 각각 모델에 넣는 방식이 된다. 



<그림 bagging 개념도>

Boosting

Boosting은 원리가 다른데 먼저 m1~3 모델이 있을때, m1에는 x에서 샘플링된 데이터를 넣는다. 그리고, 나온 결과중에서, 예측이 잘못된 x중의 값들에 가중치를 반영해서 다음 모델인 m2에 넣는다.  마찬가지로 y2 결과에서 예측이 잘못된 x’에 값들에 가중치를 반영해서 m3에 넣는다. 

그리고, 각 모델의 성능이 다르기 때문에, 각 모델에 가중치 W를 반영한다. 이를 개념적으로 표현하면 다음과 같은 그림이 된다. 



<그림 Boosting 개념도>


이를 이론적으로 다시 표현해보면

y=m1(x)+error(x) 모델 m1이 x에 대해서 제대로 예측할 확률이 y라고 하고, 이 정확도를 높이려면

error(x)를 작게 하는 모델을 적용하면 되는데, error(x) = m2(x) + error2(x)라고 볼 수 있다. 마찬가지로 error(x)를 작게 하려면 error2(x)를 작게 하면 되는데, 그러기 위해서는 여기에 다른 모델을 적용하면 된다. 즉 error2(x) = m3(x)+error3(x)가 되고, 이를 식으로 합치면


y= m1(x)+m2(x)+m3(x)+error3(x)


가 된다.

이렇게 하면 m1,m2,m3 모델의 가중치가 같기 때문에, 서로 모델이 간섭을 하여, 좋은 결과를 내기 어렵다. 그래서, 각 모델에 가중치를 반영한다.


y = W1m1(x)+W2m2(x)+W3m3(x)+error3(x)


와 같은 식이 된다. 정확한 수식은 아니지만 전체적인 개념을 이해하는 용도 정도로 이해하기 바란다. 

Boosting 기법을 이용하여 구현한 알고리즘은 Gradient Boost 가 대표적인데, 이 알고리즘을 병렬  학습이 지원되도록 구현한 라이브러리가 XGBoost 이다.

성능이 좋고, 컴퓨팅 자원 활용률이 좋아서 근래에 많이 사용되고 있고, 특히 Kaggle 상위 랭커들이 많이 사용하면서 더 유명해지고 있다.



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

댓글을 달아 주세요

  1. 유옥상 2020.01.08 15:13  댓글주소  수정/삭제  댓글쓰기

    좋은글 감사합니다. 머신러닝 알고리즘에 대해 궁금한점이 많았는데 이글 읽고 bagging이랑 boosting에 대해 이해 되었습니다. 종종 와서 글 보고 갈께요 ㅎ

  2. 호롤로롤ㄹ 2020.05.29 19:59  댓글주소  수정/삭제  댓글쓰기

    분산환경이 뭔가요?

  3. ㅁㄴㅇㅇ 2020.06.27 19:07  댓글주소  수정/삭제  댓글쓰기

    Y가 제대로 예측할 확률이면 Y = m1(x) + error(x)에서 error(x)도 높아져야 확률이 올라가는 것 아닌가요..?



쿠버네티스 애플리케이션을 위한 개발환경 설정하기

#2 VS Code

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

마이크로소프트 VS Code

다음 소개할 도구는 마이크로소프트사의 VS Code 이다. 자바 언어가 아닌 Python,node.js 등의 개발에는 VS Code 가 많이 사용되는 데, 특히 쿠버네티스 관련 플러그인들이 많아서 쿠버네티스 연동에 있어서는 IntelliJ보다 훨씬 좋은 기능을 제공한다.


설치하기

VS Code에, 쿠버네티스 개발환경을 설치해보자, 설치전에는 kubectl, docker, skaffold 가 미리 설치되어 있어야 한다. 

다음 웹 브라우져에서 이 링크(vscode:extension/GoogleCloudTools.cloudcode)를 실행하면, VS 코드에서 아래와 같이 Cloud Code 설치 화면이 나온다.



여기서 우측 화면의 Cloud Code  부분에서 인스톨 버튼을 누르면, 플러그인이 설치 된다.

설치가 완료되면 VS Code 하단에 다음과 같이 Cloud Code 아이콘이 생성된다. 

테스트 애플리케이션 만들기

플러그인 설치가 끝났으면, 테스트 애플리케이션을 하나 만들어 보자

VS Code 하단의 Cloud Code 버튼을 누르면, 아래 그림과 같이 VS Code 상단에 Text Box로, 메뉴를 선택할 수 있는 창이 나온다. 여기서 New Application을 선택한다




 그중에서  아래 그림과 같이 node.js:Hello world를 선택한다.




생성된 프로젝트의 구조는 아래 그림과 같다.



  • /kubenetes-manifests 디렉토리에는 쿠버네티스 자원에 대한 정의가 yaml 파일로 되어 있다. 이 예제에서는 service와 deployment  두 자원이 정의 된다.

  • /Dockerfile 에는 도커 이미지를 빌딩하기 위한 설정이 되어 있가

  • /skaffold.yaml 은 컨테이너 이미지를 빌드 배포하기 위한 설정이 들어 있다. 


애플리케이션 배포하기

VS Code 아래 Cloud Code 메뉴에서 Deploy application을 선택하면, 이 node.js 애플리케이션을 컨테이너로 패키징해서 Skaffold 를 통해서 쿠버네티스 클러스터에 배포한다. 


이때 몇가지를 물어보는데, 

  • build : “default”를 선택한다. 이 옵션은 컨테이너를 어디서 빌드 할것인지에 대한 옵션인데, “default”는 로컬 환경에서 docker를 이용해서 컨테이너를 만드는 옵션이고, 필요하다면, 구글 클라우드의 Cloud Build를 사용할 수 도 있다. Cloud Build는 로컬에서 빌드를 하는 것이 아니라, 빌드에 필요한 모든 파일들을 Cloud Build 클라이언트가 tar로 묶어서, 구글 클라우드의 Cloud Build로 보내서 빌드하는 방식인데, 만약에 빌드 태스크가 많은 빌드 서버라면 (Jenkins와 같은), 하나의 서버에서 여러 빌드를 할 수 없기 때문에 분산 빌드가 필요한데, Cloud Build를 사용하면, 클라우드에서 빌드를 건건이 해주기 때문에 빌드 머신의 리소스를 사용하지 않고, 여러 빌드를 동시에 진행할 수 있는 장점이 있다. 

  • 다음은 쿠버네티스 클러스터를 고르게 되는데, 이미 kubectl 이 깔려 있다면, 현재 연결되어 있는 클러스터들 중 하나를 선택할 수 있다. 


다음 그림은 설정이 끝난 후에, node.js 애플리케이션을 도커로 패키징해서 skaffold를 통해 쿠버네티스 클러스터로 배포하는 과정이다. 




빌드 및 배포가 완료되면, 아래 그림과 같이 쿠버네티스 service 의 end point URL이 출력된다.


해당 URL을 웹 브라우져에서 열어보면 아래 그림과 같이 애플리케이션을 동작하는 결과를 확인할 수 있다.


로그 및 쿠버네티스 자원 보기

VS Code에는 Kubernetes Explorer 라는 기능이 같이 설치 되는데, 이 기능은 현재 개발환경에 연결되어 있는 쿠버네티스 클러스터와 그 자원 (namespace, deployment, pods 등)을 보여준다. 

아래 그림은 개발환경에서 쿠버네티스 클러스터의 자원들을 보는 화면이다. 



앞에서 배포한 애플리케이션의 로그를 보고 싶으면 이 화면에서, 배포된 Pod의 오른쪽 버튼으로 클릭하면 “Stream Log”라는 메뉴가 나온데, 이 메뉴를 클릭하면 로그를 볼 수 있다 

위의 예제에서는 node-hello-world-649.. Pod를 선택하였다. 

아래는 실제로 출력되는 로그 결과이다. 


컨테이너로 SSH 접속하기

VS Code는 IntelliJ에 비해서 쿠버네티스에 관련된 여러가지 부가 기능을 제공하는데, 유용한 기능중 하나가 Pod의 컨테이너로 직접 SSH로 접속할 수 있는 기능이 있다.


위의 그림과 같이 Kubernetes Cluster Explorer 에서  Pod 아래 SSH로 로그인하고자 하는 컨테이너를 선택한 후에, 오른쪽 버튼을 누르고, Get Terminal 이라는 메뉴를 실행하면 아래 그림과 같이 VS Code 안에서 바로 그 컨테이너로 SSH 터미널이 열린다. 


쿠버네티스 자원 설정 보기

앞에서 쿠버네티스 클러스터들의 자원 목록을 볼 수 있었다. 다음으로 유용한 기능중의 하나는 각 자원에 대한 설정 정보를 볼 수 있는 기능이 있는데, 쿠버네티스 리소스를 선택한 후 Edit in YAML을 선택하면 해당 쿠버네티스 리소스의 YAML 파일을 볼 수 있다. 


디버깅

마지막은 애플리케이션 디버깅 기능인데, IntelliJ와 마찬가지로 쿠버네티스 클러스터로 배포된 애플리케이션에 대해서 온라인 디버깅이 가능하다. 

우측에 디버그 아이콘

을 클릭하면 디버그 뷰로 바뀌는데, 

코드에 브레이크 포인트 찝어 놓고, 디버그 모드에서 디버깅 시작 버튼


을 누르면 디버깅 모드로 변경되는데, 이렇게 해놓고, 웹 사이트를 실행하면, 아래 그림과 같이 브레이크 포인트에서 멈추고, 디버깅을 할 수 있다.




디버그 종료는 상단 디버그 툴 창


우측 붉은 버튼을 누르면 종료된다.


지금까지 간단하게, IntelliJ와 VSCode IDE에서 쿠버네티스를 위한 개발 환경 설정 방법에 대해서 알아보았다. 쿠버네티스 자체에 대한 설정과 운영/모니터링도 중요하지만, 쿠버네티스에 잘 적응하기 위해서는 개발자들이 손쉽게 개발환경에 접근할 수 있는 환경이 되어야 하기 때문에, 조직의 개발팀에 맞는 개발 환경과 프로세스를 잘 정리하기를 권장한다.

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

댓글을 달아 주세요

쿠버네티스 애플리케이션을 위한 개발환경

#1 IntelliJ
조대협 (http://bcho.tistory.com)
일반적인 경우에는 로컬 환경에서 개발하고, 로컬에서 톰캣등을 띄워서 테스트하면 되지만, 만약 부가적으로 레디스나, DB등 복잡한 개발/테스트 환경이 필요한 경우에는 결국에는 컴파일된 애플리케이션을 서버로 올려서 테스트해야 한다. 쿠버네티스를 이용해서 개발된 애플리케이션을 컨테이너로 패키징하는 개발 환경을 꾸미게 되면 개발을 조금 더 효율적으로 할 수 있다.


그렇지만 반대로, 컨테이너로 패키징해서 매번 쿠버네티스 개발용 클러스터에 배포해야 하기 때문에 복잡성이 증대하는 단점이 있다.  아래 그림은 쿠버네티스 기반의 애플리케이션을 개발할는 일반적인 개발 과정이다.


코드 작성이 끝나면, 컴파일을 해야 하고, 컴파일된 애플리케이션을 컨테이너로 패키징 해야 한다. 이때, 컨테이너의 태그 이름을 매번 바꿔줘야 하는데, 태그 이름을 변경하면 마찬가지로 YAML 파일내의 설정도 새로운 컨테이너로명으로 변경해줘야 한다. 


그리고, 변경된 내용을 쿠버네티스에 반영하면 테스트를 할 수 있는데, Cluster IP 서비스나 일반 Pod의 경우에는 외부 IP를 가지지 않기 때문에 바로 호출을 할 수 없다. 그래서 kubectl 을 이용해서 노트북의 로컬 주소를 해당 서비스나 Pod를 호출할 수 있도록 프록시 설정을 해줘야 한다.


마찬가지로 로깅 역시, 쿠버네티스 컨테이너로 돌고 있는 애플리케이션의 로그를 바로 볼 수 없기 때문에, kubectl logs 명령을 이용해서, 별도로 로그를 모니터링해야 하고, 런타임 디버깅은 지원조차 되지 않는다. 


개발된 코드가 테스트가 끝난 후에는 테스트를 위해서 기동했던 Pod 들을 삭제하는 것이 좋은데, 일반적으로, 하나의 애플레케이션은 여러가지의 리소스 (PSP, Deployment, Service, Configmap …)로 이루어지기 때문에, 일일이 삭제하려면 손이 많이 간다. 


이런 문제를 단순화 해주는 도구로 skaffold 가 로컬 환경을 지원하기 때문에, 많은 작업을 자동화할 수 있지만, 여전히 환경이 불편하다. 개발자들이 원하는 것은 쉘이나 기타 툴을 왔다갔다 하지 않고, 개발툴 (IDE)안에서 모든것을 해결하고 싶어한다.

이런 문제를 해결하기 위해서 구글에서 발표한것이 구글 클라우드 코드 (Google Cloud Code) 이다.

구글 클라우드 코드

구글 클라우드 코드는 개발도구에 확장 플러그인으로 제공되는 도구로, 위의 개발에 필요한 과정을 쿠버네티스와 연동하여  IDE안에서 모두 통합해서 지원할 수 있도록 한다. 내부 컨테이너 빌드와 배포는 Skaffold를 사용하고 있기 때문에, 운영 환경이 skaffold를 사용하고 있으면 그 설정을 공유해서 사용할 수 있다.

사실상 Skaffold를 IDE에 통합하고 몇가지 부가 기능이라고 보면 이해하기가 쉽고 사용방법도 Skaffold 설정을 그대로 사용한다. 


현재 클라우드 코드는 intelliJ와 Visual Studio Code (이하 VSCode)를 지원한다.

IntelliJ

먼저 IntelliJ에서 클라우드 코드를 사용하기 위해서는 다음 툴들이 로컬에 먼저 설치되어 있어야 한다. Docker client, kubectl, skaffold 그리고, 개발용 쿠버네티스 클러스터가 필요하다. 개발용 클러스터는 일반 쿠버네티스 환경을 지원하며, minikube도 지원한다.

설치

클라우드 코드 플러그인 설치는 File > Setting > Plugins 메뉴로 들어가서 "Cloud Code”를 검색하여 설치하면 된다.



테스트 애플리케이션 만들기

설치가 끝났으면 간단하게, Spring boot 애플리케이션을 만들어 보자

예제는 구글 클라우드에서 제공하는 Spring boot 예제 파일을 사용한다.

git clone https://github.com/GoogleCloudPlatform/cloud-code-samples.git

에서 코드를 가지고 온후에 java-hello-world project를 Maven 프로젝트로 Import 한다. 

디렉토리에는 쿠버네티스와 쿠버네티스 배포에 관련된 몇가지 파일들이 정의되어 있다


kubernetes-manifests

디렉토리에는 이 서비스를 배포하기 위한 deployment와, service YAML 파일이 정의되어 있다. 


Dockerfile

루트 디렉토리에는 애플리케이션을 패키징하기 위한 Dockerfile 이 들어있다.


skaffold.yaml

이 파일은 skaffold로 배포를 하기 위한 skaffold 설정이다. 이 파일에서는 컨테이너 이미지명과, kubernetes yaml 파일 경로등을 지정한다.

애플리케이션 배포하기




애플리케이션을 배포하기 위해서는 먼저 개발용 쿠버네티스 클러스터를 등록해야 한다.

Tools > Cloud Code > Kubernetes > Add Kubernetes Support

를 선택하면, 현재 Kubectl context에 연결된 쿠버네티스 클러스터를 자동으로 등록한다.

쿠버네티스 클러스터가 등록되면 아래 그림과 같이 상단에, 쿠버네티스 관련 메뉴가 나온다. 


여기서 Kubernetes Deploy를 선택한 후에, 오른쪽에 실행 버튼을 누르면 컴파일을 하고 쿠버네티스 클러스터로 배포를 하게 된다. 

아래 화면은 실제로 Kubernetes Continuous Deploy 모드로 배포한 결과이다. 

일반 Kubernetes Deploy 모드는, 버튼을 누를때만 배포가 되고, Continuous Deploy 모드는 코드 변화를 감지하여 코드가 변경되면 자동으로 재 배포를 한다. 


위의 그림과 같이 배포가 완료되면, 하단에 자동으로 로그 창이 떠서 컨테이너에서 실행되는 애플리케이션의 로그를 볼 수 있다. (Pod를 여러개 배포하면 여러개의 Pod 로그가 뒤섞여서 나오지만 앞에 pod 이름이 나오니까는 크게 걱정하지 않아도 된다) 

배포가 완료되면 kubectl get pod를 실행하보면 아래와 같이 java-hello-xxx pod가 배포된것을 확인할 수 있다.


테스트를 위해서, Cloud Code는 local port(PC의) 해당 서비스 포트로 자동 포워딩을 해준다. 배포가 끝난후에, 화면 하단을 보면 Port forwarding server port 라는 메세지와 함께, 로컬 포트를 쿠버네티스 Pod 포트로 어떻게 포워딩을 하는지 보여준다. 

아래 메세지는 localhost:8080 → 쿠버네티스 Pod:8080 으로 포워딩하는 것을 알리는 메시지 이다. 


단 여러 Pod 를 배포하게 되면 포트포워딩이 섞일 수 있으니, 개발시 환경에서는 하나의 Pod 만 배포하거나, Proxy를 kubectl 등으로 별도로 띄워서 서비스로 프록시를 하는것이 좋다. 

로컬에서 브라우져를 열어서 localhost:8080에 접속해보면 아래와 같이 서비스에 접속이 되서 결과가 나온다. 


디버깅

쿠버네티스 애플리케이션을 개발할 때 어려운 점이, 서버에 배포된 시스템을 디버깅 하기가 어렵다는 점인데, (쿠버네티스가 아니더라도 서버에 배포된 시스템을 리모트로 디버깅하는 것은 쉽지 않다)

클라우드 코드의 장점중의 하나가 쿠버네티스 컨테이너에서 돌고 있는 애플리케이션을 디버깅할 수 있는 기능이다.


디버깅을 위해서는 아래 그림과 같이 디버깅을 하고자하는 브레이크 포인트에, 20라인 처럼 붉은색으로 브레이크 포인트 처리를 하고 (붉은 점이 있는 부분을 클릭하면 브레이크 포인트 처리가 된다)



디버깅은 상단 메뉴에서 Kubernetes Continuos Deploy 모드로 맞춘후에, 아래 그림과 같이 오른쪽 녹색 디버깅 버튼을 누르면 된다. 



그후 애플리케이션을 실행하면 아래 그림과 같이 브레이킹 포인트에서 애플리케이션 실행이 멈추고 디버깅에 필요한 쓰레드 정보, 변수명등을 보면서 디버깅이 가능하다. 




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

댓글을 달아 주세요



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


쿠버네티스 스펙은 거의 다 보고 테스트 해봐서 멀티 클라우드와 쿠버네티스 에코 시스템을 살펴보다가 그동안 봐야지 하면서 쟁겨 놓았던 Rancher를 다시한번 볼 기회가 있어서 들여다 보았다.


퍼블릭 클라우드와 온프렘 상의 쿠버네티스의 설치 및 통합 관제를 가능하게 해주는 솔루션인데, 사실 이부분은 구글 클라우드의 Anthos와 개념상으로 겹치는 부분이 있어서 크게 메리트가 있을까 하는 의구심은 있었지만, 주변에 온프렘 설치를 Rancher를 많이 이용한다는 이야기를 들어서 한번 훝어 보았다.


일단 기본 설치는 간단하게 도커 이미지로 설치를 한 후에, 실행을하면 되기 때문에 별다른 설정이나 어려움은 없다. 새로운 클러스터 생성도 메이저 클라우드 3사를 모두 지원하고 있고, 온프렘도 vSphere 도 지원한다. 추가 클라우드 Provier를 설치하면, Baidu, 화웨이등 다른 클라우드들도 함께 지원한다. 



메뉴얼을 보니, RKE (https://rancher.com/docs/rke/latest/en/) 라고 Bare metal에 설치할 수 있는 쿠버네티스 버전도 있다. 

새로운 클러스터를 다양한 인프라에서 생성할 수 도 있지만, 반대로 이미 생성된 클러스터를 등록해서 통합 관제를 할 수 있다. 

계정 관리

일반 오픈소스 버전의 쿠버네티스는 계정 관리 시스템이 없고, 이를 외부 계정 관리 시스템과 연동해서 쓰도록 되어 있는데, Rancher를 사용할 경우, 일반 계정 시스템을 자체적으로 가지고 있고(로컬 데이타 베이스에 저장한다.) 그리고 필요하면 외부 계정시스템 (AD, GitHub,LDAP)을 사용하거나 SAML/SSO 인증등을 지원한다.


앱 카달로그

앱 카달로그를 Helm 연동을 통해서 지원하는데, 어려운 기능은 아니지만 아무래도 통합이 되어 있으니 꽤 괜찮다. 특히 멀티 클라우드 환경을 사용하면, 같은 패키지를 여러개의 쿠버네티스 클러스터에 설치할 일이 있을텐데, 이 부분이 꽤나 장점이 되지 않을듯 싶다.


모니터링

초기 대쉬 보드가 간단해서 우숩게 봤는데, 생각보다 꽤나 쓸만하다.


디폴트 대쉬 보드에서 위와 같이 기본적인 Node 정보들을 다 볼 수 있고, 그 아래 Cluster Metric에서는 Node 들의 상세 정보를, 그 아래 Kubernetes Components Metric을 들어가면 API 서버의 지표들이 나오는데, 이정보들은 모두 요약버전이고, Grafana 아이콘을 누르면 상당히 자세한 정보를 아래 그림과 같이 Grafana로 보여준다


<그림. Grafana 로 모니터링한 node 상세 정보>



<그림. 배포된 Pod에 대한 상세 정보 >


지표들을 보면 아주 상세하게 나오지는 않지만, 그래도 꽤 자세한 지표를 간단한 대쉬보드를 통해서 같은 뷰로 모니터링할 수 있다는 기능은 꽤 매력적이지 않나 싶다.


이러한 모니터링 기능들은 모니터링 기능을 활성화 해야 사용이 가능한데, 활성화 하는 부분을 보니 Prometheus 인터페이스를 사용한다. 그 말인즉슨, 현재 보이는 지표들 보다 훨씬 많은 지표를 표현할 수 있다는 말이 된다.

Alert

지표에 따른 알람 기능도 제공한다. 


산식을 보면, CPU 사용률이 100 이상인 것이 3분 지속되면 Alert 을 날리도록 하였다.

즉 지표를 정의하고,지표에 대한 목표값과 지속 시간을 정의한 개념인데, 이 기능이면 충분히 구글 SRE(DEVOPS 컨셉)의 SLI와  SLO를 현실적으로 구현이 가능하다.

Notifier

Alert 을 어디로 보낼까 했더니, Notification을 등록하는 기능이 있다.

Notifier 등록화면을 보니 이메일 뿐만 아니라 Pager Duty, Slack 그리고 Webhook 도 지원한다. 


위쳇도 지원되는데, 아쉽게도 카톡은 없다.


로깅



어쩔 수 없는 것은 알겠는데, 자체 로깅 시스템이 없고, 외부 로깅 시스템과 연동하도록 되어 있다.

ELK, Splunk를 지원하며, Kafka를 통해 로그를 빼내거나 Syslog/fluentd를 이용해서 로그 서버에 전송되 가능하다. 합리적인 디자인이라는 것에 동의는 하는데, 클릭 한번에 스택 드라이버등이나 기타 3’rd party log saas를 연동 시켜줬으면 어땠을까 하다.

기본 검토 의견

약간 모자르다. 생각을 많이 하고, 대부분의 기능을 충족시키는 것에는 동의하는데, 만들다가만 느낌이다. 특히 GUI 부분이나 기타 툴들이 무언가를 만들었다기 보다는 Grafana, Logging 시스템등 오픈 소스 시스템들을 누덕누덕 붙여놓은 느낌이다.


그렇지만 여러 클라우드의 여러 쿠버네티스 클러스터를 한눈에 관리 및 모니터링할 수 있는 점은 확실히 강점이 된다. 그리고 Prometheus 를 통한 같은 지표를 기반으로 모든 클러스터를 관리할 수 있고, Alert/Noti가 된다는 것은 확실히 장점이 되는것 같다.

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

댓글을 달아 주세요

쿠버네티스 고급 스케쥴링

#3 리소스 부족 (Resource starving) 관리


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


request와 limit의 개념이 있기 때문에 생기는 문제인데, request 된 양에 따라서 컨테이너를 만들었다고 하더라도, 컨테이너가 운영이되다가 자원이 모자르면 limit 에 정의된 양까지 계속해서 리소스를 요청하게 된다.

 컨테이너의 총 Limit의 양이 실제 시스템이 가용한 resource의 양보다 많을 수 있는 경우가 발생한다. 처음 CPU resource가 1 CPU 였는데, limit이 4이면,Pod 가 배포된 후에, 동작하다가 limit 양까지 증가되서 4 CPU가 되어 버릴 수 있다. 이때 node의 물리 CPU가 4 이면, CPU 리소스 부족이 발생할 수 있다. 이렇게 limit의 총량이 물리 resource의 총량 보다 많은 상황을 over committed 상태라고 한다.


Node에서 리소스 부족이 발생하면 쿠버네티스 클러스터는 이 상태를 해결하기 위해서 부족한 리소스를 회수하기 시작한다. 

리소스에 종류에 따라서 다른 동작을 하는데, 크게 Compressible 리소스와 Un-compressible 리소스에 따라 동작 방식이 다르다.

Compressible resource

Compressible resource는 CPU와 같이 할당된 리소스 양을 줄일 수 있는 리소스이다. 리소스 부족이 발생하면, node에 배포된 Pod의 CPU를 현재 사용하고 있는 양에서 초반 생성할때 할당된 request 크기까지 강제적으로 줄여서 리소스 부족을 해결한다. 이를 throttling 이라고 한다

이 경우 문제는 CPU 사용량일 request 이상으로 증가한 것은 그만큼 그 Pod가 애플리케이션을 동작시키기 위해서 CPU가 필요하였기 때문에 추가 할당되었던 것이다. Request 크기 까지 줄이면 할당된 CPU양을 강제적으로 줄이는 것이기 때문에, 성능 저하가 발생할 수 있다.

이런 문제를 예방하려면, Pod를 배포할때 request와 limit 값을 같이 주면, throttling은 발생하지 않지만 정확히 얼마의 CPU를 할당해야 하는지가 중요하다. 그래서 Pod 별로 적정한 CPU양을 계산하기 위해서는 개발/테스트 환경에서 request/limit 값을 서로 다르게 준 상태에서 부하 테스트를 통해서 적정 CPU 양을 찾아낸 후에 그 값을 적용하는 것이 좋다. 

Un-compressible resource

메모리나 로컬 디스크 공간은 compressible resource에 해당하지 않기 때문에, 강제적으로 Throttle을 할 수 없다. 그래서 쿠버네티스 클러스터는 리소스를 수거하기 위해서 리소스 사용량이 많은 Pod를 강제로 종료 시킨다. 

로컬 디스크의 경우에는 Pod를 강제 종료 시키기전에, 사용되지 않은 컨테이너 이미지등 사용되지 않은 공간을 삭제 해서 먼저 공간 확보를 시도하고, 그래도 공간이 모자르면 우선 순위에 따라서 Pod를 하나 삭제 한다. 삭제된 Pod는 컨트롤러 (ReplicaSet 등)에 의해서 관리가 되고 있으면 자동으로 다른 Node에서 생성된다. 

메모리의 경우에는 캐슁되는 이미지공간등이 없기 때문에, 바로 Pod를 우선순위에 따라서 삭제한다. 이를 Eviction이라고 한다.

우선 순위를 결정하는 로직은 복합적인 값들이 관여되는데, PodPriority 등, Pod 의 우선 순위등이 관련되는데, 대체적으로 Pod의 리소스 사용량이 request에 비해서 오버된 양이 가장 큰 Pod를 우선적으로 삭제 한다. 

예를 들어서 request가 3G, 4G 인 Pod가 각각 있고, 사용량이 5G, 4G 이면, request가 3G인 Pod가 사용량이 2G가 초과했고, 4G인 Pod는 초과하지 않았기 때문에, request가 3G인 Pod를 삭제(Evict)하게 된다. 

Vertical Pod Auto-scaler (VPA)

Pod의 Resource(CPU,Memory)  적절 request를 결정하는 다른 방법으로는 Vertical Pod Auto-scaler(VPA)를 사용하는 방법이 있다. 현재 beta 기능인데, 쿠버네티스 1.11 버전 이상에 별도로 추가 설치해야 한다. 

참고 : https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler

VPA는 리소스로 설정해서 Deployment에 적용할 수 있다.


apiVersion: autoscaling.k8s.io/v1beta2

kind: VerticalPodAutoscaler

metadata:

  name: my-app-vpa

spec:

  targetRef:

    apiVersion: "extensions/v1beta1"

    kind:       Deployment

    name:       my-app

  updatePolicy:

    updateMode: "Auto"

출처 : https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler


위의 예제는 my-app 이라는 Deployment에 VPA를 적용한 예제인데, VPA는 Auto 모드와 Manual 모드가 있다. 설정은 updatePolicy.updateMode에서 지정하면 된다. 

Auto 모드로 하면, VPA가 적절한 CPU와 Memory을 측정한 후에, Pod 내의 컨테이너 resource의 request 값을 적절한 값으로 변경해준다. 대신 주의해야할 점은 resource의 request를 변경하는 유일한 방법은 Pod를 리스타트 하는 방법이기 때문에 VPA를 Auto 모드로 적용한 경우 원하지 않은 Pod 리스타트가 발생할 수 있다.


만약에 Manual 모드로 설정하고, Pod를 운영하면, VPA가 직접 request 내용을 변경하지 않고, 적절하게 필요한 request 양을 추천 해준다. VPA를 만들어서 Deployment에 적용한 후 수분 정도 운영하다가 kubetctl get vpa 명령으로 내용을 보면 추천되는 request 양을 알려준다. 

% kubectl get vpa [VPA 이름]  --output yaml

다음은 위의 명령을 실행한 결과 예제이다. 

....

  recommendation:

    containerRecommendations:

    - containerName: my-rec-container

      lowerBound:

        cpu: 25m

        memory: 262144k

      target:

        cpu: 25m

        memory: 262144k

     upperBound:

        cpu: 7931m

        memory: 8291500k


위의 결과에서 출력된 target 값이 추천 resource 양이다. CPU 25m, 메모리 262144k 가 추천되는 값이다.

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

댓글을 달아 주세요

쿠버네티스 고급 스케쥴링 기법

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

Affinity

Taint가 Pod가 배포되지 못하도록 하는 정책이라면, affinity는 Pod를 특정 Node에 배포되도록 하는 정책이다. affinity는 Node를 기준으로 하는 Node affinity와, 다른 Pod가 배포된 위치(node) 를 기준으로 하는 Pod affinity 두 가지가 있다. 

Node affinity 

Node affinity는 Pod가 특정 node로 배포되도록 하는 기능이다. 예전에 label에서 설명했던 node selector 도 node의 label과 pod의 selector label이 매칭되는 node에만 배포하도록 하기 때문에, 사실상 Node affinity와 같은 기능을 한다 

Node affinity는 Hard affinity와 Soft affinity가 있다. Node affinity는 Pod가 조건이 딱 맞는 node 에만 배포되도록 하는 기능이고, Soft affinity는 Pod가 조건에 맞는 node에 되도록(반드시가 아니라)이면 배포되도록 하는 기능이다. 앞에서 언급한 node selector는 Hard affinity에 해당한다. 


아래는 Pod 설정 YAML에서 node affinity를 적용한 예제이다. 


pod-with-node-affinity.yaml docs/concepts/configuration  

apiVersion: v1

kind: Pod

metadata:

  name: with-node-affinity

spec:

  affinity:

    nodeAffinity:

      requiredDuringSchedulingIgnoredDuringExecution:

        nodeSelectorTerms:

        - matchExpressions:

          - key: kubernetes.io/e2e-az-name

            operator: In

            values:

            - e2e-az1

            - e2e-az2

      preferredDuringSchedulingIgnoredDuringExecution:

      - weight: 1

        preference:

          matchExpressions:

          - key: another-node-label-key

            operator: In

            values:

            - another-node-label-value

  containers:

  - name: with-node-affinity

    image: k8s.gcr.io/pause:2.0



출처 : From : https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


requiredDuringSchedulingIgnoredDuringExecution는 Hard affinity 정의 이다. nodeSelectorTerms 부분에, matchExpressions을 사용하여, label set-based selector 문법을 이용하면 된다.

위의 예제는 node에 label key “kubernetes.io/e2e-az-name” 의 값이 eze-az1 이나 eze-az2 인 node를 선택하도록 하는 설정이다. 


Node affinity는 여러 affinity를 동시에 적용할 수 있는데, 위의 예제에서는 두 개의 Affinity를 정의하였다. 두번째 Affinity는 Soft affinity로, preferredDuringSchedulingIgnoredDuringExecution: 으로 정의한다. Soft affinity는 앞서 언급한것과 같이 조건에 맞는 node로 되도록이면 배포될 수 있도록 그 node로 배포 선호도를 주는 기능이다. 이때 weight 필드를 이용해서 선호도를 조정할 수 있는데, weight은 1~100이고, node 의 soft affinity의 weight 값들을 합쳐서 그 값이 높은 node를 우선으로 고려하도록 우선 순위를 주는데 사용할 수 있다. 


특정 Node로 배포되게 하는 Affinity 설정도 있지만, 반대로 특정 Node로 배포되는 것을 피하도록 하는 AntiAffinity라는 설정도 있다. nodeAffinity 대신 nodeAntiAffinity라는 Notation을 사용하면 되고, Affinity와는 다르게 반대로, 조건에 맞는 Node를 피해서 배포하도록 되낟. 

Inter-Pod affinity

Node affinity가 node의 label을 기준으로 Pod가 배포될 node는 선택한다면, Inter pod affinity는 기존에 배포된 Pod를 기준으로 해서, 배포될 node를 결정한다. 

Pod affinity는 데이타 베이스의 Master / Slave pod 가 다른 node 에 배포되도록 하기 위해서 master pod가 배포된 node를 피해서 배포하게 한다던가. 클러스터 시스템에서 클러스터를 이루는 각각의 Pod가 다른 node에 배포도록 하는 등에 전략에 사용할 수 있다. 


Node affinity와 마찬가지로 Hard affinity와 Soft affinity가 있다. Node affinity아 마찬가지로 requiredDuringSchedulingIgnoredDuringExecution 로 hard affinity를,preferredDuringSchedulingIgnoredDuringExecution로 soft affinity를 정의한다.

Node를 선택할때 Inter pod affinity는 node affinity와 다르게 topology key 라는 것을 사용한다. 

Pod affinity는 Pod affinity에 의해서 해당 node를 선택한 후에, 그 node의 label을 하나 선택한다. 선택하는 label은 topology key로 지정하는데, 이 topology key에 매칭 되는 node 들을 배포 대상으로 선택한다. Node affinity와 마찬가지로 특정 Pod와 같이 배포되는 것을 피하는 AntiAffinity 도 있다. Pod affinity는 podAffinity라는 notation을 사용하고, Anti affinity는 PodAntiAffinity라는 Notation을 사용한다. 


개념이 복잡하기 때문에 아래 예제를 보면서 이해해보도록 하자


apiVersion: v1
kind: Pod
metadata:
  name: with-pod-affinity
spec:
  affinity:
    podAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchExpressions:
          - key: security
            operator: In
            values:
            - S1
        topologyKey: cloud/zone
  containers:
  - name: with-pod-affinity
    image: k8s.gcr.io/pause:2.0


출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity



와 같이 Pod 설정 파일이 있다고 했을때, 배포는 다음 그림과 같다. 



  PodAffinity에서 requiredDuringSchedulingIgnoredDuringExecution 로 Hard affinity를 정의하였다. 이렇게 되면, 기존에 배포된 Pod 중에서, key가 “security”이고, value가 “S1”인, Pod1이 배포된 node인 Node 1을 기준으로 하는데, topologyKey로 정의된 cloud/zone label의 값”z1”을 기준 Node인 Node 1에서 읽어서, Node 들 중에 label이 “cloud/zone=z1” 인 Node 들만을 후보로 선택해서, Node 1, Node 2 를 배포 가능 Node 로 선택한다.


Inter-Pod affinity 예제

다른 예제를 보자. 아래 예제는 redis-cache Pod를 3개 배포하는데, 각각 다른 Node에 분산되서 배포되도록 하는 예제이다. 


apiVersion: apps/v1

kind: Deployment

metadata:

  name: redis-cache

spec:

  selector:

    matchLabels:

      app: store

  replicas: 3

  template:

    metadata:

      labels:

        app: store

    spec:

      affinity:

        podAntiAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - store

            topologyKey: "kubernetes.io/hostname"

      containers:

      - name: redis-server

        image: redis:3.2-alpine


출처 : https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


PodAntiAffinity를 사용한 예로, requiredDuringSchedulingIgnoredDuringExecution 를 이용한 Hard affinity이다. Pod의 label이 “app=store”가 있는 Pod가 배포되어 있는 Node 중에서 topologyKey가 kubernetes.io/hostname (쿠버네티스에서 자동으로 미리 저장하는 label로 Node의 이름을 정의하는 label 이다.) 로 되어 있기 때문에, node의 kubernetes.io/hostname label 값이 이 Node와 다른 Node를 배포 타겟으로 설정한다. 즉 “app=store” 이름으로 배포된 Pod가 없는 Node에 배포하는 설정이다.

이 Redis pod는 “app=store” 라는 label을 가지고 있는 Pod이기 때문에, 이미 Node에 이 Redis Pod가 배포되어 있으면, 그 Node에는 배포되지 않기 때문에 Redis Pod가 배포되지 않은 다른 Node에 중첩되서 배포되지 않도록 해준다. 


이 상태에서 nginx 서버를 배포해보자. Nginx pod를 3개 배포하는데, 각각을  서로 다른 node에 배포하도록 하되, 대신 redis가 배포된 node에 배포하도록 하는 설정이다.  


apiVersion: apps/v1

kind: Deployment

metadata:

  name: web-server

spec:

  selector:

    matchLabels:

      app: web-store

  replicas: 3

  template:

    metadata:

      labels:

        app: web-store

    spec:

      affinity:

        podAntiAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - web-store

            topologyKey: "kubernetes.io/hostname"

        podAffinity:

          requiredDuringSchedulingIgnoredDuringExecution:

          - labelSelector:

              matchExpressions:

              - key: app

                operator: In

                values:

                - store

            topologyKey: "kubernetes.io/hostname"

      containers:

      - name: web-app

        image: nginx:1.12-alpine


출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity


먼저 podAntiAffinity로 “app:web-store” Pod가 배포되어 있는 node를 찾은 후에, 그 Node이 kubernetes.io/hostname 를 topologyKey로 해서, 그 node들을 제외한다.

즉 “app:web-store” 라벨을 가지고 있는 Pod 들이 배포된 Node를 제외하는 설정이다. 그런데, 이 Pod는 label을 “app:web-store” 라벨을 가지도록 되어 있기 때문에, 이 Pod가 배포되어 있는 Node에 배포하지 말고, 다른 Node에 분산해서 배포하라는 내용이다.


여기에 PodAffinity로 “app:store”인 Pod가 배포되어 있는 Node에서 topologyKey로 “kubernetes.io/hostname”을 사용하였기 때문에, “app:store”인 라벨을 가지고 있는 Pod가 배포되어 있는 Node를 찾아서 배포하라는 내용이다. 다시 말해서 “app:store”인 Pod와 같은 Node에 배포하라는 의미이다. 


아래는 실행결과 인데, web-server가 각각 다른 Node에 겹치지 않고 분리되서 부탁되어 있는 것을 볼 수 있고, redis-cache Pod도 역시 서로 다른 Node에 겹치지 않게 배포되어 있는 것을 확인할 수 있다. 그리고 web-server와 redis-cache pod들은 하나씩 같은 Node에 배포된것을 확인할 수 있다. 


NAME                           READY STATUS RESTARTS AGE       IP NODE

redis-cache-1450370735-6dzlj   1/1 Running 0 8m        10.192.4.2 kube-node-3

redis-cache-1450370735-j2j96   1/1 Running 0 8m        10.192.2.2 kube-node-1

redis-cache-1450370735-z73mh   1/1 Running 0 8m        10.192.3.1 kube-node-2

web-server-1287567482-5d4dz    1/1 Running 0 7m        10.192.2.3 kube-node-1

web-server-1287567482-6f7v5    1/1 Running 0 7m        10.192.4.3 kube-node-3

web-server-1287567482-s330j    1/1 Running 0 7m        10.192.3.2 kube-node-2

출처  https://kubernetes.io/docs/concepts/configuration/assign-pod-node/#affinity-and-anti-affinity

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

댓글을 달아 주세요

  1. 2020.07.18 16:44  댓글주소  수정/삭제  댓글쓰기

    비밀댓글입니다

자연어 처리 - 단어 표현 방법

빅데이타/머신러닝 | 2019. 8. 4. 18:28 | Posted by 조대협

자연어 처리

Word representation

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


머신러닝을 위해서 단어를 수치화 하는 방법을 Word Representation이라고 한다.

가장 쉬운 방법으로는 One-hot-encoding을 사용하는 방법이 있지만, 이 방법의 경우에는 단어간의 의미를 수식화할 수 없고, 특히나 단어가 많을 경우에는 단어를 표현하는 벡터(행렬)의 크기가 커지며, 그중에서 하나의 필드만 1로 처리하기 때문에 공간 소모가 심하다. 


그래서 분포 가설(Distributed hypothesis)를 기반으로한 방식이 많이 사용되는데, 분포 가설(Distributed hypothesis) 란, 같은 문맥의 단어는 비슷한 의미를 갖는다는 개념으로 두 가지 방식이 많이 사용된다.

Count based 

문장에 동시에 등장하는 단어의 수를 카운트 해서 이를 벡터화 하는 방식

동시에 등장하는 횟수를 Co-occurrence라고 하고, 이를 행렬화 하는 방식은 

  • SVD (Singular Value Decomposition) : 특이값 분해

  • LSA (Latent Semantic Analysis) : 잠재적 의미 분해

  • HAL (Hyperspace Analogue to Language)

등이 있음

Predictive

NN이나 모델을 이용해서, 특정 문맥에 어떤 단어가 나올것을 예측하면서 벡터를 만드는 방법

  • NNLM (Neural Network Language Model)

  • RNNLM(Recurrent Neural Network Language Model) 

  • Word2Vec

    • CBOW (Continuous Bag of Words)
      문장을 통해서 단어를 예측
      예) 오늘은 저녁에 ??나 먹어야 겠다. 

    • Skip-Gram ← 가장 많이 사용함
      단어를 통해 전체 문장을 예측
      예) ??? ???? 짜장면이나 ??? ??


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

댓글을 달아 주세요

쿠버네티스 고급 스케쥴링 기법

#1 스케쥴링과 Taint&Toleration

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

쿠버네티스 스케쥴링

쿠버네티스에서 Pod 를 생성 요청 했을때, Pod를 적정 node에 배치하는 것을 스케쥴링이라고 한다. Pod를 어느 node에 배치할것인가에 대해서는 생각 보다 많은 고려가 필요하다. 먼저 Pod가 생성하기 위한 충분한 리소스 (CPU와 메모리)가 남아 있어야 하고, 디스크 볼륨을 사용할 경우, attach하고자 하는 디스크 볼륨이 해당 node에서 접근이 가능해야 한다.

또한 애플리케이션 특성에 따라서, Pod의 배포에 대해 배려가 필요한 경우도 있다. 예를 들어 MySQL을 HA 모드로 배포하기 위해서 마스터, 슬레이브 노드 각각을 배포하고자 할때, 마스터 슬레이브가 같은 node에 배포되게 되면, 해당 node가 문제가 생기면 마스터,슬레이브 노드 모두가 서비스가 불가능해지기 때문에, HA에 의한 가용성을 지원할 수 없다. 그래서 마스터 슬레이브 노드를 다른 node에 배포해야 하고, 더 나아가 다른 rack, 클라우드의 경우에는 다른 Zone(존)에 배포될 필요가 있다. 

이 모든 것을 제어 하는 것을 스케쥴링이라고 한다. 

이 장에서는 쿠버네티스의 스케쥴링이 어떻게 작동을 하는지 그리고, 이 스케쥴링을 제어할 수 있는 고급 기법에 대해서 알아보고자 한다. 

스케쥴링 작동의 기본 원리

(참고 : 쿠버네티스의 스케쥴링 정책은 이 에 매우 잘 설명되어 있다. )

Pod 생성이 요청 되면, 쿠버네티스 스케쥴러는 먼저 필터라는 것을 이용해서, Pod가 배포될 수 있는 Node를 선정하는 작업을 한다.

크게 보면 세 종류의 필터를 사용하는데, 다음과 같다.

  • 볼륨 필터

  • 리소스 필터

  • 토폴로지 필터

각각을 살펴보자

볼륨필터

Pod를 생성할 때, 생성하고자 하는 Pod의 디스크 볼륨에 대해서 Node가 지원할 수 있는지를 확인한다. 

예를 들어 클라우드에서 생성되는 Pod가 zone 1에 생성된 디스크를 attach해야 하는 조건을 가지고 있을때, 특정 클라우드들의 경우 다른 zone의 디스크를 attach할 수 없기 때문에, zone 1 이외에 있는 Node들을 후보에서 제외하고, 해당 볼륨을 attach할 수 있는 Node 들만 후보로 남긴다.

또는 쿠버네티스에서는 사용자가 볼륨에 node-affinity를 정의해서 특정 node 에만 그 볼륨을 attach할 수 있도록 하는데, 이러한 조건에 부합하지 않는 Node들을 제거하고 후보 Node 리스트를 만든다.

리소스 필터

다음으로 적용되는 필터가 리소스 필터인데, 해당 Node 들이 Pod를 배포할만한 충분한 리소스 (CPU,Memory,Disk)가 있는지를 확인하는 단계이다.

CPU 와 메모리 여유분이 Pod가 요청한 만큼 충분한지, 그리고 Node의 디스크 공간도 확인하는데, 앞에서 언급한 디스크 볼륨과 다소 차이가 있는 것이, Node가 Pod를 실행하기 위해서는 Pod를 실행하기 위한 디스크 공간이 필요하다. Pod 의 컨테이너 이미지를 저장하기 위한 공간등이 이에 해당한다. 

CPU,Memory,Disk 뿐 아니라 네트워크 포트도 체크를 하는데, Pod가 Node 포트를 사용하는 Pod 일 경우, 예를 들어 Pod가  Node의 8080 포트를 사용하고자 하는데, 이미 해당 Node의 8080 포트가 다른 Pod에 의해서 점유된 경우, 새로운 Pod를 생성할 수 없기 때문에 그 Node를 Pod를 생성하기 위한 Pod 리스트에서 제외한다. 


일반적인 경우에는 볼륨 필터와 리소스 필터를 거친 Node들을 후보로 두고 이 중에서 적절한 Node를 선택해서 Pod를 배포한다. 

고급 스케쥴링 정책

Pod를 배포할때, 사용자가 특정 Node를 선택할 수 있도록 정책을 정의할 수 있다. 예를 들어 앞에서 언급한것과 같이 MySQL의 마스터, 슬레이브가 같은 Node에 배포되지 않도록 Pod의 스케쥴링 정책을 인위적으로 조정할 수 있다. 이를 고급 스케쥴링 기법이라고 하는데, 자세한 설명은 이 문서를 참고하기 바란다. 

Taint & Toleration

먼저 살펴볼 스케쥴링 정책은 Taint와 Toleration이다.

Taint는 Node에 정의할 수 있고, Toleration은 Pod에 정의할 수 있는데, 한마디로 쉽게 설명하면, Taint 처리가 되어 있는 Node에는 Pod가 배포되지 않는다. Taint 처리가 되어 있는 Node에는 Taint에 맞는 Toleration을 가지고 있는 Pod 만 배포될 수 있다.

Taint

Taint는 label과 유사하게 <key>=<value>:<effect> 형태로 정의되서 node에 적용된다. key와 value는 사용자가 마음대로 정할 수 있으며, effect는 NoSchedule, PreferNoSchedule,NoExecute 3가지로 정의할 수 있다. NoSchedule은 taint 처리가 되어 있는 node에 대해서는 Pod가 이에 맞는 toleration을 가지고 있다면 이 Node에는 그 Pod를 배포하지 못하도록 막는 effect 이다. (나머지 2가지 effect에 대해서는 뒤에서 설명한다.)

Node에 taint 를 적용하는 방법은 다음과 같다.

%kubectl taint node [NODE_NAME] [KEY]=[VALUE]:[EFFECT]

형태로 적용하면 된다.

예를 들어 gke-terry-gke11-default-pool-317bb64b-21kd Node에 key가 “node-type”이고, value가 “production”이고, Effect가 NoSchedule인 Taint를 적용하고자 하면 다음과 같이 명령을 실행하면 된다. 

 

%kubectl taint node gke-terry-gke11-default-pool-317bb64b-21kd node-type=production:NoSchedule

node/gke-terry-gke11-default-pool-317bb64b-21kd tainted


이렇게 taint를 적용한 후, Taint가 제대로 적용이 되었는지, kubectl get nodes gke-terry-gke11-default-pool-317bb64b-21kd -o yaml 명령을 이용해서 확인해보면 다음과 같이 taint가 적용되어 있는 것을 확인할 수 있다. 


apiVersion: v1

kind: Node

metadata:

: (중략)

   name: gke-terry-gke11-default-pool-317bb64b-21kd

: (중략)

spec:

: (중략)

  taints:

  - effect: NoSchedule

    key: node-type

    value: production


이렇게 Taint 처리가 된 Node는 알맞은 Toleration이 정의되지 않은 Pod는 배포될 수 없다. 


Node에 Taint를 적용하는 방법은 앞에서 설명한 것과 같이 node 이름을 정의해서 하나의 특정 Node에 적용하는 방법도 있지만, node 에 적용된 label을 이용하여, label이 일치 하는 여러개의 node에 동시에도 적용할 수 있다. 

방법은 아래와 같이 -l 옵션을 이용해서 적용하고자 하는 node의 label의 key/value를 적용하면 된다. 

%kubectl taint node -l [LABEL_KEY]=[LABEL_VALUE] [KEY]=[VALUE]:[EFFECT]


예를 들어서 아래와 같이 -l  옵션을 적용하면,

%kubectl taint node -l node-label=zone1 node-type=production:NoSchedule

Node 중에서 label이 node-label=zone1인 모든 node에, node-type=production:NoSchedule 인 Taint가 적용된다. 

Toleration

그러면 Taint 처리가되어 있는 Node에 Pod를 배포하기 위해서 사용하는 Toleration이란 무엇인가?

Toleration처리가 되어 있는 Node에 배포될 수 있는 일종의 티켓과 같은 개념이라고 생각하면 된다. Taint 처리가 되어 있는 Node에 Toleration이라는 티켓을 가지고 있으면, 그 Node에 Pod가 배포될 수 있다. (“배포된다"가 아니라, “배포될 수 있다" 라는 의미에 주의하도록 하자. 그 Node가 아니라 다른 조건에 맞는 Node가 있다면, 배포될 수 있다.)

Toleration의 정의는 Match operator를 사용하여 Pod Spec에 정의한다.

tolerations:

- key: "key"

  operator: "Equal"

  value: "value

  effect: "NoSchedule"


이렇게 정의하면, key,value,effect 3개가 Taint와 일치하는 Node에 Pod가 배포될 수 있다. 

조금 더 광범위하게 정의를 하려면, “Exist”를 사용하면 된다. 

tolerations:

- key: "key"

  operator: "Exists

  effect: "NoSchedule"


이렇게 정의하면, Taint에 위에서 정의한 Key가 있고, effect가 “NoSchedule”로 설정된 Node에 value 값에 상관 없이 배포될 수 있다.

또는 아래와 같이 tolerations 절에서 effect 항목을 제외하면, 해당 key로 Taint가 적용되어 있는 모든 Node에 대해서 이  Pod를 배포하는 것이 가능하다. 

tolerations:

- key: "key"

  operator: "Exists


Taints는 특정 nodes에 일반적인 Pod가 배폭되는 것을 막을 수 있다. 가장 좋은 예로는 쿠버네티스의 마스터 Node에 적용된 Taints가 이에 해당한다. 쿠버네티스 마스터 Node에는 관리를 위한 Pod만이 배포되어야 하기 때문에, 일반적인 Pod를 배포할 수 없도록 Taints가 이미 적용되어 있고, 마스터 Node에 Pod를 배포하기 위해서는 이에 맞는 Toleration을 가지고 있어야 한다. 

이 외에도 운영용 Node로 특정 Node들을 적용해놓고, 개발이나 스테이징 환경용 Pod이 (실수로라도) 배포되지 못하게 한다는 것등에 사용할 수 있다. 

Taint와 Toleration 개념 정리

앞에서 Taint와 Toleration의 개념과 사용법에 대해서 설명하였는데, 이를 이해하기 편하게 그림으로 정리해서 보자




<그림. Taints와 Toleration의 개념>

출처 : https://livebook.manning.com/#!/book/kubernetes-in-action/chapter-16/section-16-3-1


Master node에는 node-role.kubernetes.io/master 라는 key로 value 없이 effect만 “NoSchedule”로 Taint를 정의하였다. toleration을 가지지 못한 일반적인 Pod는 Master node에는 배포될 수 없고, Taint 처리가 되어 있지 않은 regular node에만 배포가 가능한다.

System pod의 경우 node-role.kubernetes.io/master 라는 key로, effect가 “NoSchedule”인 toleration을 가지고 있기 때문에, Taint가 없는 Regular node에는 당연히 배포가 가능하고, Toleration에 맞는 Taint를 가지고 있는 Master node에 배포될 수 있다. 

Taint Effect 

Taint와 Toleration에 대한 사용법과 개념을 이해하였으면, 이제 Taint effect에 대해서 조금 더 자세하게 알아보도록 하자. 앞에서도 설명했듯이 Taint에 적용할 수 있는 effect는 아래와 같이 3가지가 있다. 

  • NoSchedule : Pod가 배포되지 못한다. (Toleration이 일치하면 배포됨)

이 effect로 Taint가 적용된 Node는 일치하는 Toleration을 가지고 있는 Pod가 아닌 경우에는 배포되지 못한다. 단, 이는 새롭게 배포되는 Pod에만 적용되고 이미 배포되어 있는 Pod에는 적용되지 않는다. 다시 말해서, Node 1에 Pod 1이 돌고 있는데, 이 Node 1에 Taint를 적용하면, Taint 적용전에 배포되서 돌고 있는 Pod 1에는 영향을 주지 않는다. Pod 1는 알맞은 Toleration이 없더라도, 종료되서 새롭게 스케쥴링이 되지 않는 이상 Node 1에 배포된 상태로 동작한다. 


만약에 이미 돌고 있는 Pod들에게도 영향을 주려면 NoExecute 라는 effect 를 사용하면 된다. 

  • NoExecute :  돌고 있던 Pod들을 evit 하고(다른 node로 옮김), 새것들은 못들어 오게 한다.

이 effect는 NoSchedule과 유사하지만, 새롭게 배포되는 Pod 뿐만 아니라, 이미 그 Node 에서 돌고 있는 Pod 들에게도 영향을 줘서, NoExecute로 Taint가 적용되면, 이에 해당하는 Toleration을 가지고 있지 않는 Pod는 모두 evict 되서 그 node에서 삭제 된다. 물론 ReplicaSet/Deployment 등 Controller에 의해서 관리되는 Pod의 경우에는 Taint 처리가 되어 있지 않은 다른 Node에서 새롭게 생성된다. 


이 effect에 대해서는 tolerationSeconds 라는 패러미터를 고려해야 하는데, 이 Taint가 적용된 Node에 맞는 toleration 을 가지고 있는 Pod의 경우, 이 Node에 영구적으로 남아 있지만, Pod의 toleration에 tolerationSeconds 패러미터가 정의되어 있으면 이 시간만큼만 남아 있다가 evit 된다. 즉 Pod 1, Pod 2,Pod 3가 Node 1에서 돌다가,  Node 1 에 NoExecute effect로 Taint가 적용되었다고 했을때, Pod 1은 이 Taint에 맞는 Toleration을 가지고 있고

Pod 2는 이 Taint에 맞는 Toleration을 tolerationSeconds=300(초) 패러미터와 함께 정의되어 가지고 있다면

Pod 3는 아마 Toleration이 없다면 

Pod 1은 계속 Node1에 남아 있게 되고,  Pod 2는 Node 2에 300초 동안 남아있다가 evit (강제 종료)되며, Pod 3는 바로 강제 종료가 된다. 


  • PreferNoSchedule : 가급적 Pod 배포하지 않는다. 


마지막으로 소개할 effect는 PerferNoSchedule인데, NoSchedule의 소프트 버전으로 생각히면 된다. NoSchedule로  Taint 처리가 되어 있는 Node 라면, 스케쥴시에, Toleration을 가지고 있지 않은 Pod는 무조건 배포가 불가능하지만, PreferNoSchedule의 경우에는 Toleration이 없는 Pod의 경우에는 되도록이면 배포되지 않지만 리소스가 부족한 상황등에는 우선순위를 낮춰서, Toleration이 없는 Pod도 배포될 수 있도록 한다.

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

댓글을 달아 주세요

  1. 평범 2020.08.04 17:44  댓글주소  수정/삭제  댓글쓰기

    오타가 있는거 같습니다.
    --------------------------------------------------------------------------------
    NoSchedule은 taint 처리가 되어 있는 node에 대해서는 Pod가 이에 맞는 toleration을 가지고 있다면 이 Node에는 그 Pod를 배포하지 못하도록 막는 effect 이다.
    --------------------------------------------------
    여기서 포드가 해당하는 toleration 을 가지고 있으면 배포가 가능한게 아닌가요?

  2. onevibe12 2020.09.23 14:05  댓글주소  수정/삭제  댓글쓰기

    댓글 다신 평범님의 말씀이 맞습니다. 적으시다가 순간 헷갈리셨나봐요 ㅇㅅㅇ..

쿠버네티스를 위한 CD 툴, Skaffold #2

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

Skaffold 설정 파일의 구조

Skaffold의 개념과 기본적인 사용법을 이해하였으면, 다음으로 Skaffold 설정 파일에 대해서 알아보도록 하자.

Skaffold의 설정 파일은 아래와 같이 크게 두가지가 있다. 

  • Pipeline config
    우리가 앞에서 살펴본 skaffold.yaml 파일이 파이프라인 설정 파일에 해당한다.
    컨테이너 빌드 및 레지트리 등록, 테스트 및 컨테이너 배포 일련의 파이프라인에 대한 행동을 정의한다. 

  • Global config
    ~/.skaffold/config 파일에 저장되어 있는 정보로 skaffold의 기본 설정 정보를 정의한다. 예를 들어 디폴트 도커 레지스트리 경로등을 정의한다. 

pipeline config (skaffold.yaml)

파이프라인 설정은 앞에서 언급했듯이, 컨테이너 빌드, 빌드된 컨테이너 등록, 테스트 그리고 쿠버네티스 클러스터에 컨테이너와 쿠버네티스 리소스를 배포 하는 파이프라인 흐름을 정의하는데, 크게 build,test,deploy 그리고 profiles 4 가지 영역으로 구분된다. 개념적으로 표현해보면 아래와 같다. 


<그림 skaffold.yaml 파일에서 정의한 파이프라인 별 영역>


각 영역을 살펴보자

build

build는 컨테이너 빌드와, 빌드된 컨테이너에 대한 태깅 그리고 태그된 컨테이너 이미지를 컨테이너 레지트스리에 등록하는 역할을 정의한다


Builder

Skaffold는 각 행위를 정의 하기 위해서 단계별로 사용할 수 있는 컴포넌트들을 정의하는데, 빌드에 관련된 내용은 Builder에 의해서 처리된다.

Builder는 컨테이너 이미지를 빌드 하는 역할을 하는데, 다음 플랫폼 들을 지원한다.

  • 로컬 Docker 를 이용해서 Dockerfile을 기반으로 컨테이너를 빌드

  • Dockerfile을 기반으로 구글 클라우드의 Google Cloud Build를 이용해서 리모트로 빌드

  • Dockerfile을 기반으로 쿠버네티스 클러스터에서 빌드를  할 수 있는 Kaniko 를 이용한 빌드

  • 로컬에서 Bazel(구글이 만든 make와 같은 빌드 스크립트)

  • jlib를 이용하여 maven이나 gradle 프로젝트를 로컬에서 빌드 

  • jlib를 이용해서 Google Cloud Build 를 이용하여 리모트에서 빌드

  • 또는 커스텀 빌드 스크립트를 이용한 빌드

등이 있다. 


Tagger

이렇게 빌드된 이미지들은 컨테이너 레파지토리에 저장되는데, 컨테이너 레파지토리의 경로는 앞에서 언급한 global config 에 의해서 정의 된다.

레파지토리로 컨테이너가 푸쉬 되기 전에, 컨테이너 이미지의 이름에 태그(tag)를 부여해야 하는데, 이는 Tagger에 의해서 실행 된다.

컨테이너의 태그로 사용할 수 있는 것들은 다음과 같다.

  • dateTime : 날짜와 시간을 태그로 사용한다.

  • sha256 : 컨테이너 이미지의 sha256 해쉬 값을 추출하여 태그로 사용한다.

  • 환경변수 : 환경 변수로 정의된 값을 태그로 사용한다. 

  • gitcommit ID : git commit ID를 태그로 사용한다.


추천 하는 방법은 git를 사용하는 경우 가능하면 git commit ID를 사용하는 것이 코드 변경에 대한 컨테이너 이미지에 대한 추적성을 제공하기 때문에 이 방법이 가장좋고, 또는 dateTime을 사용하면, 일자/시간에 따라서 컨테이너 이미지 변경을 추적할 수 있기 때문에 이 방법도 추천한다. 


아래는 Tagger를 이용하여 빌드 과정에서 컨테이너 태그를 dateTime으로 하도록 하는 예제이다. 


apiVersion: skaffold/v1beta11

kind: Config

build:

 tagPolicy:

   dateTime:

     format: "2006-01-02_15-04-05.999_MST"

     timezone: "Local"

 artifacts:

 - image: gcr.io/terrycho-personal/node-example

     : (중략)

이렇게 skaffold.yaml 파일을 작성한 후에, skaffold run을 실행한 결과의 일부를 보면 다음과 같다.


       : (중략)

Successfully built 852d1d5a8f90

Successfully tagged gcr.io/terrycho-personal/node-example:2019-06-26_23-22-58.301_KST

The push refers to repository [gcr.io/terrycho-personal/node-example]

5ba8e7a42d0b: Preparing

520c8d5849cb: Prepari...


컨테이너의 태그 이름이 앞에서 지정한 날짜와 시간형식으로 지정된것을 확인할 수 잇다. 

test

테스트 단계는 그 위에 탑재되는 애플리케이션을 테스트 하지는 않다. 대신 컨테이너가 제대로 만들어 졌는지 특히 파일들이나 디렉토리들은 원하는 대로 제대로 배포되었는지 등을 확인하는데, skaffold에서는 이러한 컨테이너 테스트를 container-structure-test  라는 프레임웍을 사용해서 진행한다. 

deploy

이렇게 만들어지고 테스트된 컨테이너 이미지는 쿠버네티스로 배포하게 된다. 

배포는 Deployer 를 이용해서 쿠버네티스에 배포되는데,

쿠버네티스 디폴트 CLI인 kubectl 뿐만 아니라, helm, kustomize 를 모두 지원한다.  

Helm을 사용하게 되면, Helm Chart를 기반으로 새로운 릴리즈를 만들어서 배포를 한다. 

아래는 skaffold-helm 을 이용해서 배포한 결과인데, Helm 의 새로운 릴리즈가 생성된것을 확인할 수 있다. 


%helm list

NAME         REVISION UPDATED                 STATUS  CHART              APP VERSION NAMESPACE

skaffold-helm 1       Thu Jun 27 00:10:10 2019 DEPLOYED skaffold-helm-0.1.0           default  


Helm과 Skaffold를 이용하면 장점이 Helm의 버전 관리를 이용해서 버전 관리가 간편해지고, 리소스를 삭제할때도 Helm이 의존되는 리소스를 모두 같이 삭제해주기 때문에 깔끔한 배포 및 자원 관리가 가능하다. 



profile

일반적으로 하나의 애플리케이션을 개발하면, 여러환경을 함께 써야 하는 경우가 있다. 예를 들어 단일 코드를 개발,QA,운영 환경등 여러환경에 배포해야 할 경우가 있다. 

아래 예제 그림을 보면, git 의 소스코드를 개발 환경에서는 리파지토리에 저장하지 않고, 로컬의 minikube 클러스터에 배포하도록 해서 개발 테스트를 하고, 운영 환경에서는 빌드된 도커 컨테이너 이미지를 리파지토리에 저장한 다음, 다른 리소스들과 함께 Helm 으로 패키징해서 운영용 쿠버네티스 클러스터에 배포하도록 한다. 




이런 여러 종류의 배포 파이프라인을 하나의 skaffold 설정안에서 관리하기 위해서 skaffold는 profile이라는 개념을 가지고 있다. 

skaffold에서 profile 정의는, 디폴트 설정이 있고, 디폴트 설정과 다른 부분만 profiles 섹션에서 정의하면 된다. 아래는 dev 와 production 두개의 프로파일을 정의한 내용이다. 


apiVersion: skaffold/v1beta11

kind: Config

build:

 artifacts:

 - image: gcr.io/terrycho-personal/node-example

   context: backend

   sync:

     manual:

     # Sync all the javascript files that are in the src folder

     # with the container src folder

     - src: 'src/**/*.js'

       dest: .

profiles:

- name : dev

 build:

   local:

     push: false

- name : production


production은 디폴트 설정과 다르지 않게 설정하였기 때문에, profile 설정 부분에는 별다른 다른 내용을 넣지 않는다. dev에서 build 부분을 로컬 빌드로 하고, 도커 리파지토리로 이미지를 푸쉬하지 않도록 설정하였다.

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

댓글을 달아 주세요

  1. Joo 2019.07.04 13:14 신고  댓글주소  수정/삭제  댓글쓰기

    와~ 좋네요. 써봐야겠어요 :)

쿠버네티스용 Continuous Deployment 툴인 Skaffold

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

What is skaffold?

쿠버네티스 기반에서 개발을 하고 테스트를 하려면 일반적으로 다음과 같은 절차를 거쳐야 한다.

  • 소스 코드를 수정한 후, 

  • 수정한 코드를 컴파일 한 다음에

  • 컴파일한 소스 코드를 포함해서 Dockerfile을 이용해서 컨테이너로 패키징 한후에

  • 컨테이너를 레파지토리 새로운 버전 태그를 붙여서 업로드 하고

  • 쿠버네티스의 기존 Deployment나 Pod의 yaml 파일에 image 명을 바꾼후

  • kubectl -f apply 를 이용해서 변경된 파일을 반영하고,

  • 다음 public IP가 있는 서비스의 경우에는 public IP로 접속하고, 아닌 경우에는 SSH 터널링을 이용해서 해당 Pod나 Service에 접근해서 테스트를 한다.

한번의 소스 코드 수정을 하고 나서, 이러한 일련의 과정을 거쳐야 하는데, 코드 수정 하나를 반영하기 위해서 해야 할일들이 많다.

이러한 문제를 해결하기 위해서 코드 수정이 쿠버네티스에 반영되기 까지의 과정을 자동화를 통해서 단순화 해주는 프레임워크가 skaffold 이다. 

아래 그림은 skaffold의 개념인 워크 플로우를 도식화한 그림이다. 


<그림. Skaffold의 워크플로우>

출처 https://skaffold.dev/docs/concepts/


Skaffold가 실행되면, Skaffold는 소스 코드 리포지토리(ex git) 또는 로컬 디렉토리를 모니터링 한다. 소스 코드의 변화가 생기면 자동으로 코드를 빌드하고, 이를 도커 컨테이너로 패키징 한다. 

다음 컨테이너화된 이미지에 태그를 붙이는데, 단순하게 SHA256 해쉬를 생성하거나 또는 날짜나 Git commit ID를 태그로 사용한다. Git commit ID를 태그로 사용할 경우에는 코드 변경 내용이 어느 컨테이너 버전에 반영되어 있는지 추적할 수 있는 추적성을 제공한다.

태깅이된 컨테이너 이미지를 지정되어 있는 컨테이너 리파지토리에 저장하고, 미리 정의되어 있는 쿠버네티스 YAML 파일을 이용하여 변경 내용을 대상 쿠버네티스 클러스터에 반영한다. 이 과정은 개발자가 코드 변경만 하게 되면 전 과정이 자동으로 진행되기 때문에, 개발 환경으로의 코드 반영에 대해서 수동 작업이 필요없고, Skaffold를 종료 시키게 되면, 개발을 위해서 생성된 쿠버네티스 리소스 (Service, Pod 등)을 자동으로 삭제해주기 때문에, 매우 쾌적한 개발환경에서 개발이 가능하다.


원래 컨셉 자체는 CD(Continuous Deployment)의 용도이지만, 로컬 환경에서 변경된 소스코드를 바로 쿠버네티스에 배포 해주기 때문에 특히나 개발 환경 구축에 편리한 장점을 가지고 있다.

Hello Skaffold

상세한 개념 이해에 앞서서 먼저 간단한 예제를 통해서 어떻게 동작하는지 살펴보도록 하자

준비

예제 환경에는 쿠버네티스 클러스터가 이미 하나가 있어야 하고, kubectl이 로컬(랩탑)에 설치되어 쿠버네티스 클러스터와 연결이 되어 있는 상태이어야 한다. 

설치

설치 방법은 공식 문서 https://skaffold.dev/docs/getting-started/ 를 참고하면된다.

MAC의 경우에는 아래와 같이 curl 명령어를 이용해서 바이너리를 다운로드 받은 후 사용하면 된다.


curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-darwin-amd64

chmod +x skaffold

sudo mv skaffold /usr/local/bin


예제코드 다운로드

예제 코드는 GoogleContainerTools 깃허브에서 다운로드 받을 수 있다.


%git clone https://github.com/GoogleContainerTools/skaffold


예제코드는 examples 디렉토리에 있는데, 그 안에 node 폴더의 내용을 이용하도록 한다.

%cd examples/node


예제 디렉토리 안에는 다음과 같은 파일들이 있다.

  • backend/Dockerfile :

  • backend/package-lock.json , package.json

  • backend/src/ : node.js 소스 코드

  • k8s/ : 쿠버네티스 리소스 배포용 yaml 파일

  • skaffold.yaml : Skaffold 워크플로우를 정의한  yaml 파일


node.js 소스 코드

src/index.js 는 node.js 소스 코드로, node.js express 웹 프레임웍을 이용해서 “Hello World!” 문자열을 리턴하는 웹 서버를 3000 번 포트로 기동 시키는 코드이다.


const express = require('express')

const { echo } = require('./utils');

const app = express()

const port = 3000


app.get('/', (req, res) => res.send(echo('Hello World!')))


app.listen(port, () => console.log(`Example app listening on port ${port}!`))

소스 코드 이외에 필요한 패키지들을 package.json에 정의되어 있다.

Dockerfile

Dockerfile의 내용은 다음과 같다. 


FROM node:10.15.3-alpine


WORKDIR /app

EXPOSE 3000

CMD ["npm", "run", "dev"]


COPY package* ./

RUN npm install

COPY . .


알파인 리눅스 기반의 node.js 10.15-3 이미지를 베이스 이미지로 하고, /app 디렉토리에 package.json 과 기타 소스코드를 복사 한후에, npm install로 의존성 패키지를 설치하고, package.json에 지정된 nodemon src/index.js 명령을 이용해서 앞에서 작성한 index.js node.js 코드를 실행하도록 한다. 

그리고 EXPOSE 3000 명령어를 이용해서 도커 컨테이너에서 3000번 포트로 Listen을 하도록 한다.

쿠버네티스 설정 파일

그러면 앞에서 만들어진 컨테이너로 쿠버네티스에서 서빙을 하기 위해서 쿠버네티스 리소스를 정의해야 하는데, ./k8s/deployment.yaml에서  Service와, Deployment를 정의 하도록 하였다.


apiVersion: v1

kind: Service

metadata:

  name: node

spec:

  ports:

  - port: 3000

  type: LoadBalancer

  selector:

    app: node

---

apiVersion: apps/v1

kind: Deployment

metadata:

  name: node

spec:

  selector:

    matchLabels:

      app: node

  template:

    metadata:

      labels:

        app: node

    spec:

      containers:

      - name: node

        image: gcr.io/terrycho-personal/node-example

        ports:

        - containerPort: 3000


여기서 컨테이너 리포지토리의 경로는 자신이 사용하는 경로로 변경해야 한다. 이 예제에서는 구글 클라우드 리포지토리 (GCR)에 저장하도록  경로를 지정하였다.

Skaffold 설정 파일

다음으로 skaffold의 설정 파일을 보자.


apiVersion: skaffold/v1beta11

kind: Config

build:

  artifacts:

  - image: gcr.io/terrycho-personal/node-example

    context: backend

    sync:

      manual:

      # Sync all the javascript files that are in the src folder

      # with the container src folder

      - src: 'src/**/*.js'

        dest: .


컨테이너 이미지의 경로를 정해놓고, 소스 코드 파일을 어떻게 sync 할지를 지정한다. sync.manual 로 로컬 디렉토리의 src/**/*.js 파일이 변경이 되면 이를 반영하도록 지정하였다. 

실행

그러면 해당 파일을 실행해보도록 하자 skaffold dev 명령어를 실행하면 위의 파일들을 기반으로 컨테이너화를 하고, deployment.yaml 을 이용해서 쿠버네티스 리소스 (Service, Deployment)를 생성 한후, 컨테이너를 배포하여 서비스할 수 있는 형태로 준비한다. 

이때 --port-foward 옵션을 줄 수 있는데, 위의 예제의 경우에는 Service가 있고, Service 타입이 Load balancer이기 때문에, External IP를 가질 수 있어서, 쿠버네티스에서 배정된 External Service를 통해서 Pod에 접근해 서비스가 가능하지만, 일반적으로 개발을 할때는 Service 까지 배포할 필요가 없고 단순하게 Pod만 배포하고 싶은 경우가 있다. (단순하게 작업하기 위해서)

이때 --port-forward 옵션을 주면 Pod에서 외부로 오픈된 포트로 로컬 환경(랩탑) 포트 Forwarding을 해준다.

즉 이 예제에서는 로컬(랩탑)에서 localhost:3000번을 Pod의 3000번 포트로 포트 포워딩을 해주게 된다.  이 옵션을 추가해서 실행하게 되면 결과는 아래와 같이 된다. 


%skaffold dev --port-forward

Generating tags...

 - gcr.io/terrycho-personal/node-example -> gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a

Tags generated in 91.565177ms

Starting build...

Building [gcr.io/terrycho-personal/node-example]...

Sending build context to Docker daemon  101.4kB

Step 1/7 : FROM node:10.15.3-alpine

 ---> 56bc3a1ed035

Step 2/7 : WORKDIR /app

 ---> Running in d4579ab15f5a

Removing intermediate container d4579ab15f5a

 ---> 7c725818faae

Step 3/7 : EXPOSE 3000

 ---> Running in e05be7cb896b

Removing intermediate container e05be7cb896b

 ---> 49ac353388c6

Step 4/7 : CMD ["npm", "run", "dev"]

 ---> Running in 52b216a93e63

Removing intermediate container 52b216a93e63

 ---> 3b7878ac4c42

Step 5/7 : COPY package* ./

 ---> 457e460aae8d

Step 6/7 : RUN npm install

 ---> Running in ed391e236197


> nodemon@1.18.7 postinstall /app/node_modules/nodemon

> node bin/postinstall || exit 0


Love nodemon? You can now support the project via the open collective:

 > https://opencollective.com/nodemon/donate


npm WARN backend@1.0.0 No description

npm WARN backend@1.0.0 No repository field.

npm WARN backend@1.0.0 No license field.

npm WARN optional SKIPPING OPTIONAL DEPENDENCY: fsevents@1.2.4 (node_modules/fsevents):

npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.2.4: wanted {"os":"darwin","arch":"any"} (current: {"os":"linux","arch":"x64"})


added 265 packages from 161 contributors and audited 2359 packages in 5.381s

found 1 high severity vulnerability

  run `npm audit fix` to fix them, or `npm audit` for details

Removing intermediate container ed391e236197

 ---> f7a7217f7a60

Step 7/7 : COPY . .

 ---> 5b713462d7ac

Successfully built 5b713462d7ac

Successfully tagged gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a

The push refers to repository [gcr.io/terrycho-personal/node-example]

5723b6d27633: Preparing

4f0634afaa8a: Preparing

51a09ffa90b0: Preparing

4789f3ba672b: Preparing

28bf756b6f8e: Preparing

4c299e1e70d5: Preparing

f1b5933fe4b5: Preparing

4c299e1e70d5: Waiting

f1b5933fe4b5: Waiting

28bf756b6f8e: Layer already exists

4c299e1e70d5: Layer already exists

f1b5933fe4b5: Layer already exists

51a09ffa90b0: Pushed

4789f3ba672b: Pushed

5723b6d27633: Pushed

4f0634afaa8a: Pushed

v0.32.0-28-g6bd1d50a: digest: sha256:bccf087827dd536481974eb28465ce4ce69cf13121589e4a36264ef2279e9d1d size: 1786

Build complete in 27.759843507s

Starting test...

Test complete in 17.963µs

Starting deploy...

kubectl client version: 1.11

kubectl version 1.12.0 or greater is recommended for use with Skaffold

service/node created

deployment.apps/node created

Deploy complete in 1.606308148s

Watching for changes every 1s...

Port Forwarding node-6545db86c5-wkdc4/node 3000 -> 3000

[node-6545db86c5-wkdc4 node] 

[node-6545db86c5-wkdc4 node] > backend@1.0.0 dev /app

[node-6545db86c5-wkdc4 node] > nodemon src/index.js

[node-6545db86c5-wkdc4 node] 

[node-6545db86c5-wkdc4 node] [nodemon] 1.18.7