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


Archive»


 

'초보'에 해당되는 글 51

  1. 2018.08.11 쿠버네티스 #15 - 모니터링 (3/3) 구글 스택드라이버를 이용한 쿠버네티스 모니터링
  2. 2018.07.16 쿠버네티스 #13 - 모니터링 (1/2)
  3. 2018.07.06  쿠버네티스 #11 - ConfigMap
  4. 2018.06.23 쿠버네티스 #8 - Ingress (3)
  5. 2018.06.18 쿠버네티스 #7 - 서비스 (Service) (3)
  6. 2018.06.17 쿠버네티스 #6 - 실제 서비스 배포해보기 (2)
  7. 2018.06.11 쿠버네티스 #5 - 디스크 (볼륨/Volume) (1)
  8. 2018.05.30 쿠버네티스 #3- 개념이해 (2/2) 컨트롤러
  9. 2018.05.21 쿠버네티스 #2 - 개념 이해 (1/2)
  10. 2018.05.19 쿠버네티스 #1 - 소개
  11. 2018.02.21 배포 자동화 솔루션 Spinnaker - #2 설치
  12. 2018.01.01 Apache Beam (Dataflow)를 이용하여, 이미지 파일을 tfrecord로 컨버팅 하기
  13. 2017.12.19 NMF 알고리즘을 이용한 유사한 문서 검색과 구현(1/2)
  14. 2017.12.11 구글 스택드라이버를 이용한 애플리케이션 로그 모니터링
  15. 2017.10.20 수학포기자를 위한 딥러닝과 텐서플로우의 이해 (14)
  16. 2017.10.18 분류모델 (Classification)의 성능 평가 (2)
  17. 2017.10.11 클러스터링 #2 - Hierarchical clustering (계층 분석)
  18. 2017.09.27 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #4 - 오토인코더 기반의 신용카드 이상거래 검출코드와 분석 결과 (1)
  19. 2017.09.23 파이썬을 이용한 데이타 시각화 #1 - Matplotlib 기본 그래프 그리기 (1)
  20. 2017.09.20 오토인코더를 이용한 비정상 거래 검출 모델의 구현 #3 - 데이타 전처리 (1)
 


쿠버네티스 #15

모니터링 3/3 구글 스택드라이버를 이용한 모니터링

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



구글 클라우드 쿠버네티스 스택드라이버 모니터링

쿠버네티스 모니터링 시스템을 구축하는 다른 방법으로는 클라우드 서비스를 사용하는 방법이 있다. 그중에서 구글 클라우드에서 제공하는 스택 드라이버 쿠버네티스 모니터링에 대해서 소개하고자한다.

https://cloud.google.com/monitoring/kubernetes-engine/


현재는 베타 상태로, 구글 클라우드 쿠버네티스 서비스 (GKE)에서만 지원이 되며, 쿠버네티스 버전 1.10.2 와 1.11.0 (또는 그 상위버전)에서만 지원이 되고, 모니터링 뿐 아니라, 쿠버네티스 서비스에 대한 로깅을 스택드라이버 로깅 서비스를 이용해서 함께 제공한다.


스택드라이버 쿠버네티스 모니터링을 설정하는 방법은 간단하다. 쿠버네티스 클러스터를 설정할때, 아래 그림과 같이 Additional features 항목에서 “Try the new Stackdriver beta monitoring and Logging experience” 항목을 체크하면 된다.



클러스터를 생성한 후에, 구글 클라우드 콘솔에서 Monitoring 메뉴를 선택한 후에



스택드라이버 메뉴에서 Resources 메뉴에서 아래 그림과 같이 Kubernetes 메뉴를 선택하면 쿠버네티스 모니터링 내용을 볼 수 있다.



모니터링 구조

스택드라이버 쿠버네티스 모니터링의 가장 큰 장점 중의 하나는 단순한 단일 뷰를 통해서 대부분의 리소스 모니터링 과 이벤트에 대한 모니터링이 가능하다는 것이다.

아래 그림이 스택드라이버 모니터링 화면인데, “2”라고 표시된 부분이 시간에 따른 이벤트이다. 장애등이 발생하였을 경우 아래 그림과 같이 붉은 색으로 표현되고, 3 부분을 보면, 여러가지 뷰 (계층 구조)로 각 자원들을 모니터링할 수 있다. 장애가 난 부분이 붉은 색으로 표시되는 것을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


Timeline에 Incident가 붉은 색으로 표시된 경우 상세 정보를 볼 수 있는데, Timeline에서 붉은 색으로 표시된 부분을 누르면 아래 그림과 같이 디테일 이벤트 카드가 나온다. 이 카드를 통해서 메모리,CPU 등 이벤트에 대한 상세 내용을 확인할 수 있다.



<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >


반대로 정상적인 경우에는 아래 그림과 같이 이벤트 부분에 아무것도 나타나지 않고, 모든 자원이 녹색 동그라미로 표시되어 있는 것을 확인할 수 있다.


개념 구조

쿠버네티스 모니터링중에 어려운 점중의 하나는 어떤 계층 구조로 자원을 모니터링 하는가 인데, 이런점을 해결하기 위해서 구글 스택드라이버 쿠버네티스 모니터링은 3가지 계층 구조에 따른 모니터링을 지원한다. 모니터링 화면을 보면 아래와 같이 Infrastructure, Workloads, Services 와 같이 세가지 탭이 나오는 것을 볼 수 있다.



어떤 관점에서 클러스터링을 모니터링할것인가인데,

  • Infrastructure : 하드웨어 자원 즉, node를 기준으로 하는 뷰로,  Cluster > Node > Pod > Container 의 계층 구조로 모니터링을 제공한다.

  • Workloads : 워크로드, 즉 Deployment를 중심으로 하는 뷰로 Cluster > Namespace > Workload (Deployment) > Pod > Container 순서의 계층 구조로 모니터링을 제공한다.

  • Services : 애플리케이션 즉 Service 를 중심으로 하는 뷰로 Cluster > Namespace > Service > Pod > Container 계층 순서로 뷰를 제공한다.

Alert 에 대한 상세 정보

각 계층 뷰에서 리소스가 문제가 있을 경우에는 앞의 동그라미가 붉은색으로 표시가 되는데,  해당 버튼을 누르게 되면, Alert 에 대한 상세 정보 카드가 떠서, 아래 그림과 같이 이벤트에 대한 상세 정보를 확인할 수 있다.


<출처 : https://cloud.google.com/monitoring/kubernetes-engine/observing >

결론

지금까지 간단하게 쿠버네티스에 대한 모니터링과 로깅에 대해서 알아보았다. 프로메테우스나 그라파나와 같은 최신 기술을 써서 멋진 대쉬 보드를 만드는 것도 중요하지만 모니터링과 로깅은 시스템을 안정적으로 운영하고 장애전에 그 전조를 파악해서 대응하고, 장애 발생시에는 해결과 향후 예방을 위한 분석 및 개선 활동이 일어나야 한다. 이를 위해서 모니터링과 로깅은 어디까지나 도구일 뿐이고, 어떤 지표를 모니터링 할것인지 (SLI : Service Level Indicator), 지표의 어느값까지를 시스템 운영의 목표로 삼을 것인지 (SLO : Service Level Object)를 정하는 프렉틱스 관점이 더 중요하다.  이를 구글에서는 SRE (Site Reliability Engineering)이라고 하는데, 이에 대한 자세한 내용은 https://landing.google.com/sre/book.html 를 참고하기 바란다.

이런 프렉틱스를 구축하는데 목적을 두고, 모니터링을 위한 툴링등은 직접 구축하는 것보다는 클라우드에서 제공하는 스택 드라이버와 같은 솔루션이나 데이타독(Datadog)와 같은 전문화된 모니터링 툴로 구축을 해서 시간을 줄이고, 프렉틱스 자체에 시간과 인력을 더 투자하는 것을 권장한다.




쿠버네티스 #13

모니터링 1/2


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


시스템을 운영하는데 있어서 운영 관점에 있어서 가장 중요한 기능중의 하나는 시스템에 대한 모니터링이다. 시스템 자원의 사용량이나 에러등에 대한 모니터링을 통해서, 시스템을 안정적으로 운영하고 문제 발생시 원인 파악과 대응을 할 수 있다.

이번 글에서는 쿠버네티스 모니터링 시스템에 대한 개념과, 아키텍쳐 그리고 구축 방법에 대해서 소개하고자 한다.

쿠버네티스 모니터링 컨셉

쿠버네티스에 대한 모니터링을 보면 많은 툴과 지표들이 있어서 혼돈하기 쉬운데, 먼저 모니터링 컨셉에 대한 이해를 할 필요가 있다.

쿠버네티스 기반의 시스템을 모니터링하기 위해서는 크게 아래와 같이 4가지 계층을 모니터링해야 한다.



1. 호스트 (노드)

먼저 쿠버네티스 컨테이너를 실행하는 하드웨어 호스트 즉 노드에 대한 지표 모니터링이 필요하다. 노드의 CPU,메모리, 디스크, 네트워크 사용량과, 노드 OS와 커널에 대한 모니터링이 이에 해당한다.

2. 컨테이너

다음은 노드에서 기동되는 각각의 컨테이너에 대한 정보이다. 컨테이너의 CPU,메모리, 디스크, 네트워크 사용량등을 모니터링 한다.

3. 애플리케이션

컨테이너안에서 구동되는 개별 애플리케이션의 지표를 모니터링 한다. 예를 들어, 컨테이너에서 기동되는 node.js 기반의 애플리케이션의 응답시간, HTTP 에러 빈도등을 모니터링한다.

4. 쿠버네티스

마지막으로, 컨테이너를 컨트롤 하는 쿠버네티스 자체에 대한 모니터링을한다. 쿠버네티스의 자원인 서비스나 POD, 계정 정보등이 이에 해당한다.

쿠버네티스 기반의 시스템 모니터링에 대해서 혼돈이 오는 부분중의 하나가 모니터링이라는 개념이 포괄적이기 때문이다. 우리가 여기서 다루는 모니터링은 자원에 대한 지표 대한 모니터링이다. 포괄적인 의미의 모니터링은 로그와, 에러 모니터링등 다양한 내용을 포괄한다.  

쿠버네티스 로깅

지표 모니터링과 함께 중요한 모니터링 기능중 하나는 로그 수집 및 로그 모니터링이다.

로그 수집 및 로그 모니터링 방법은 여러가지 방법이 있지만, 오픈소스 로그 수집 및 모니터링 조합인 EFK (Elastic search + FluentD + Kibina) 스택을 이용하는 경우가 대표적이다.

Fluentd 에이전트를 이용하여, 각종 로그를 수집하여, Elastic search에 저장하고, 저장된 지표를 Kibana 대쉬 보들르 이용하여 시작화 해서 나타내는 방법이 있다.

이에 대한 자세한 설명을 생략한다.

쿠버네티스 모니터링 시스템 구축

그러면 이러한 모니터링 시스템을 어떻게 구축할 것인가?

쿠버네티스 모니터링은 버전업 과정에서 많은 변화를 겪고 있다. 기존 모니터링 시스템의 아키텍쳐는 cAdvisor,Heapster를 이용하는 구조였으나, 이 아키텍쳐는 곧 deprecated 될 예정이고, Prometheus등 다양한 모니터링 아키텍쳐가 후보로 고려 되고 있다.

아래 그래프를 보면 재미있는 통계 결과가 있는데, cAdvisor,Heapster,Promethus 를 이용하는 방법도 있지만, 클라우드의 경우에는 클라우드 벤더에서 제공하는 쿠버네티스 모니터링 솔루션을 그대로 사용하거나 (18%) 또는 데이타독이나 뉴렐릭 (Datadog, newRelic)과 같이 전문화된 모니터링 클라우드을 사용하는 비율 (26%) 도 꽤 높다.



<그림. 쿠버네티스 모니터링 솔루션 분포 >

출처 :  https://thenewstack.io/5-tools-monitoring-kubernetes-scale-production/


개인적인 의견으로는 직접 모니터링 솔루션을 구축해서 사용하는 것보다는 비용은 약간 들지만 클라우드 벤더에서 제공되는 모니터링 도구나 또는 데이타독과 같은 전문 모니터링 솔루션을 이용하는 것을 추천한다.


직접 모니터링 솔루션을 구축할 경우 구축과 운영에 드는 노력도 꽤 크고, 또한 어떠한 지표를 모니터링해야할지 등에 대한 추가적인 노하우가 필요하다. 또한 cAdvisor,Heapster,Promethues 조합은 호스트와 컨테이너 그리고 쿠버네티스에 대한 모니터링은 제공하지만 애플리케이션 지표에 대한 모니터링과 로깅 기능은 제공하지 않기 때문에 별도의 구축이 필요하다. 이런 노력을 들이는 것 보다는 모든 기능이 한번에 제공되고 운영을 대행해주는 데이타독이나 클라우드에서 제공해주는 모니터링 솔루션을 사용하는 것을 추천한다.

Heapster 기반 모니터링 아키텍처

이러한 모니터링 요건을 지원하기 위해서, 쿠버네티스는 자체적인 모니터링 컴포넌트를 가지고 있는데, 그 구조는 다음과 같다.



<그림. 쿠버네티스 모니터링 시스템 아키텍쳐>

출처 Source : https://www.datadoghq.com/blog/how-to-collect-and-graph-kubernetes-metrics/


cAdvisor

cAdvisor는 모니터링 에이전트로, 각 노드마다 설치되서 노드에 대한 정보와 컨테이너 (Pod)에 대한 지표를 수집하여, Kubelet으로 전달한다.

Heapster

cAdvisor에 의해 수집된 지표는 Heapster 라는 중앙 집중화된 지표 수집 시스템에 모이게 되고, Heapster는 수집된 지표를 스토리지 백앤드에 저장한다.

Storage backend

Heapster가 지표를 저장하는 데이타베이스를 스토리지 백앤드라고 하는데, Heapster는 확장성을 위해서 다양한 스토리지 백앤드를 플러그인 구조를 선택하여 연결할 수 있다.

현재 제공되는 대표적인 스토리지 백앤드는 구글 클라우드의 모니터링 시스템인 스택드라이버 (stackdriver), 오픈 소스 시계열 데이타베이스인 인플럭스 디비 (InfluxDB) 등을 지원한다.

그래프 대쉬 보드

이렇게 저장된 모니터링 지표는 그래프와 같은 형태로 시각화 될필요가 있는데, 스토리지 백앤드를 지원하는 다양한 시각화 도구를 사용할 수 있다. 구글의 모니터링 시스템인 스택드라이버의 경우에는 자체적인 대쉬보드 및 그래프 인터페이스가 있고, 인플럭스 디비나 프로메테우스의 경우에는 오픈소스 시각화 도구인 그라파나(Grafana)를 사용할 수 있다.


<그림. 그라파나와 프로메테우스를 연결하여, 지표 모니터링을 시각화 한 예제>


그러나 이 아키텍쳐는 deprecation 계획이 시작되서 1.13 버전 부터는 완전히 제거될 예정이다.

https://github.com/kubernetes/heapster/blob/master/docs/deprecation.md


쿠버네티스 대시보드

다른 방법으로는 쿠버네티스를 모니터링 하고 관리할 수 있는 쉬운 방법이 하나 있는데, 쿠버네티스 대시보드를 사용하는 방법이다. 쿠버네티스는 기본적으로 kubectl이라는 커맨드 라인 인터페이스 (이하 CLI : Command Line Interface)를 사용하지만, 추가적으로 웹 기반의 관리 콘솔을 제공한다. 이를 쿠버네티스 대시보드라고 한다. (https://github.com/kubernetes/dashboard)

대시 보드 설치

쿠버네티스 대시 보드 설치 방법은 간단하다. 아래와 같이 대시보드 설정 yaml 파일을 이용하면 간단하게 대시 보드를 쿠버네티스 클러스터에 설치할 수 있다.


% kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml


일반적인 경우에는 위의 스크립트로 설치가 가능하지만, 구글 클라우드 쿠버네티스 엔진의의 경우에는 설치 중에 권한 관련 에러가 나올 수 있는데, 구글 클라우드 쿠버네티스 엔진의 경우에는 보안을 이유로 일반적인 쿠버네티스보다 권한 설정 레벨이 높게 설정되어 있기 때문이다. 구글 클라우드 쿠버네티스 엔진에서 대시보드를 설치하고자할때에는 위의 스크립트를 실행하기 전에 먼저 아래 명령어를 이용해서, 현재 사용자 계정에 대해서 cluster-admin 롤을 부여해줘야 한다.  


%kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole cluster-admin --user $(gcloud config get-value account)

대시 보드 접속

대시보드 설치가 끝났으면, 대시보드를 접속해보자

대시보드는 외부 서비스로 제공되지 않고, 내부 IP로만 접속이 가능한데, 클러스터 외부에서 접근하려면 kubectl proxy를 이용하면, 간단하게 접근이 가능하다.

kubectl proxy는 로컬 머신 (예를 들어 노트북)과 쿠버네티스 클러스터간의 통신을 프록싱해줘서, 로컬 머신에서 쿠버네티스 클러스터내의 HTTP 서비스를 접근할 수 있도록 해준다.

사용 방법은 로컬 머신에서 간단하게

%kubectl proxy

명령을 실행해주면 localhost:8001 포트를 통해서 쿠버네티스 클러스터로 트래픽을 프록시 해준다.

위와 같이 proxy를 실행한후에,  아래 URL로 접근을 하면, 대시보드 콘솔에 접근할 수 있다.

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/


URL에 접근하면 아래와 같이 로그인 창이 나타난다.



사용자 계정 및 토큰등에 대해서는 보안 부분에서 별도로 다루기로 하겠다.

대쉬보드를 사용하기 위해서는 사용자 인증이 필요한데, 간단하게 인증을 위한 토큰을 사용하는 방법을 이용하도록 하겠다.

토큰은 쿠버네티스 API 인증 메커니즘중의 하나로, 여기서는 admin-user라는 계정을 하나 만든후에, 그 계정에, 클러스터 관리자롤을 부여한 후에, 그 사용자의 토큰을 사용하는 방법을 사용하겠다.


먼저 아래 스크립트를 이용해서 admin-user 라는 사용자를 생성한다.

admin-user.yaml 파일

apiVersion: v1

kind: ServiceAccount

metadata:

 name: admin-user

 namespace: kube-system


다음 아래 스크립트를 이용해서 cluster-admin 롤을 앞에서 생성한 admin-user에 부여한다.

admin-rolebinding.yaml 파일

apiVersion: rbac.authorization.k8s.io/v1beta1

kind: ClusterRoleBinding

metadata:

 name: admin-user

roleRef:

 apiGroup: rbac.authorization.k8s.io

 kind: ClusterRole

 name: cluster-admin

subjects:

- kind: ServiceAccount

 name: admin-user

 namespace: kube-system


다음 아래 명령어를 이용하면 admin-user의 토큰 값을 알 수 있다.

% kubectl -n kube-system describe secret $(kubectl -n kube-system get secret | grep admin-user | awk '{print $1}')


명령을 실행하면 아래와 같이 토큰이 출력된다.


이 토큰 값을 앞의 로그인 창에 입력하면, 대시보드에 로그인할 수 있다.

대시 보드에 로그인하면 아래와 같이 노드나, Pod, 서비스등 쿠버네티스의 자원의 대부분의 정보에 대한 모니터링이 가능하다.




또한 kubectl CLI 명령을 사용하지 않고도 손쉽게 Deployment 등 각종 자원을 생성할 수 있다.


로그 부분에 들어가면 아래와 같이 로그 정보를 볼 수 있다



재미있는 기능중 하나는 아래 그림과 같이 특정 Pod의 컨테이너를 선택하면, 웹콘솔상에서 해당 컨테이너로 SSH 로그인이 가능하다.



여기서 다룬 쿠버네티스 대시보드 설정 및 로그인 부분은 프록시 사용, 로그인을 토큰을 사용하는 등, 운영환경에는 적절하지 않은 방법이다. 개발환경이나 테스트 용도로만 사용하도록 하고, 운영 환경에서는 사용자 계정 시스템 생성과 적절한 권한 배정을 한 후에, 적절한 보안 인증 시스템을 마련한 후에 적용하도록 하자.




쿠버네티스 #11

ConfigMap


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



애플리케이션을 배포하다 보면, 환경에 따라서 다른 설정값을 사용하는 경우가 있다. 예를 들어, 데이타베이스의 IP, API를 호출하기 위한 API KEY, 개발/운영에 따른 디버그 모드, 환경 설정 파일들이 있는데, 애플리케이션 이미지는 같지만, 이런 환경 변수가 차이가 나는 경우 매번 다른 컨테이너 이미지를 만드는 것은 관리상 불편할 수 밖에 없다.

이러한 환경 변수나 설정값들을 변수로 관리해서 Pod가 생성될때 이 값을 넣어줄 수 있는데, 이러한 기능을 제공하는 것이 바로 Configmap과 Secret이다.


아래 그림과 같이 설정 파일을 만들어놓고, Pod 를 배포할때 마다 다른 설정 정보를 반영하도록 할 수 있다.



Configmap이나 secret에 정의해놓고, 이 정의해놓은 값을 Pod로 넘기는 방법은 크게 두가지가 있다.

  • 정의해놓은 값을 Pod의 환경 변수 (Environment variable)로 넘기는 방법

  • 정의해놓은 값을 Pod의 디스크 볼륨으로 마운트 하는 방법

ConfigMap

configmap은 앞서 설명한것과 같이 설정 정보를 저장해놓는 일종의 저장소 역할을 한다.

configmap은 키/밸류 형식으로 저장이된다.

configmap을 생성하는 방법은 literal (문자)로 생성하는 방법과 파일로 생성하는 방법 두가지가 있다.

Literal

먼저 간단하게 문자로 생성하는 방법을 알아보자

키가 “language”로 하고 그 값이 “java”인 configMap을 생성해보자

Kubectl create configmap [configmap 이름] --from-literal=[키]=[값] 식으로 생성하면 된다.

아래 명령을 이용하면, hello-cm 이라는 이름의 configMap에 키는 language, 값은 java인 configMap이 생성된다.

% kubectl create configmap hello-cm --from-literal=language=java


또는 아래와 같이 YAML파일로도 configMap을 생성할 수 있다.

hello-cm.yaml

apiVersion: v1

kind: ConfigMap

metadata:

 name: hello-cm

data:

 language: java


데이타 항목에 [키]:[값] 형식으로 라인을 추가하면 여러개의 값을 하나의 configMap에 저장할 수 있다.

configmap이 생성되었으면 이 값을 Pod에서 환경 변수로 불러서 사용해보도록 하자.

node.js로 간단한 웹 애플리케이션을 만든후에 “LANGUAGE”라는 환경 변수의 값을 읽어서 출력하도록 할것이다.

아래와 같이 server.js node.js 애플리케이션을 만든다.


var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end(" my prefered language is "+process.env.LANGUAGE+ "\n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 파일을 컨테이너로 패키징한 후에, 아래와 같이 Deployment를 정의한다

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-literal

 template:

   metadata:

     name: cm-literal-pod

     labels:

       app: cm-literal

   spec:

     containers:

     - name: cm

       image: gcr.io/terrycho-sandbox/cm:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       env:

       - name: LANGUAGE

         valueFrom:

           configMapKeyRef:

              name: hello-cm

              key: language


configMap에서 데이타를 읽는 부분은 맨 아래에 env 부분인데, env 부분에 환경 변수를 정의하는데, name은 LANGUAGE라는 이름으로 정의하고 데이타는 valueFrom을 이용해서 configMap에서 읽어오도록 하였다. name에는 configMap의 이름인 hello-cm을, 그리고 읽어오고자 하는 데이타는 키 값이 “language”인 값을 읽어오도록 하였다. 이렇게 하면, LANGUAGE 환경 변수에, configMap에 “language” 로 저장된 “java”라는 문자열을 읽어오게 된다.


이 스크립트를 이용하여 Deployment를 생성한 후에, 이 Deployment 앞에 Service (Load balancer)를 붙여 보자.


apiVersion: v1

kind: Service

metadata:

 name: cm-literal-svc

spec:

 selector:

   app: cm-literal

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


서비스가 생성이 되었으면 웹 브라우져에서 해당 Service의 URL을 접속해보자.



위와 같이 환경 변수에서 “java”라는 문자열을 읽어와서 출력한것을 확인할 수 있다.

File

위와 같이 개개별 값을 공유할 수 도 있지만, 설정을 파일 형태로 해서 Pod에 공유하는 방법도 있다.

예제를 보면서 이해하도록 하자.

profile.properties라는 파일이 있고 파일 내용이 아래와 같다고 하자

myname=terry

email=myemail@mycompany.com

address=seoul


파일을 이용해서 ConfigMap을 만들때는 아래와 같이 --from-file 을 이용해서 파일명을 넘겨주면 된다.

kubectl create configmap cm-file --from-file=./properties/profile.properties

이렇게 파일을 이용해서 configMap을 생성하면, 아래와 같이 키는 파일명이 되고, 값은 파일 내용이 된다.


환경변수로 값을 전달하기

생성된 configMap 내의 값을 Pod로 전달하는 방법은,앞에서 예를 든것과 같이 환경 변수로 넘길 수 있다.

아래 Deployment 예제를 보면


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-file-deployment

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-file

 template:

   metadata:

     name: cm-file-pod

     labels:

       app: cm-file

   spec:

     containers:

     - name: cm-file

       image: gcr.io/terrycho-sandbox/cm-file:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       env:

       - name: PROFILE

         valueFrom:

           configMapKeyRef:

              name: cm-file

              key: profile.properties


cm-file configMap에서 키가 “profile.properties” (파일명)인 값을 읽어와서 환경 변수 PROFILE에 저장한다. 저장된 값은 파일의 내용인 아래 문자열이 된다.

myname=terry

email=myemail@mycompany.com

address=seoul


혼동하지 말아야 하는 점은, profile.properties 파일안에 문자열이 myname=terry 처럼 키/밸류 형식으로 되어 있다고 하더라도, myname 을 키로 해서 terry라는 값을 가지고 오는 것처럼 개개별 문자열을 키/밸류로 인식하는 것이 아니라 전체 파일 내용을 하나의 문자열로 처리한다는 점이다.


디스크 볼륨으로 마운트하기

configMap의 정보를 pod로 전달하는 방법은 앞에 처럼 환경 변수를 사용하는 방법도 있지만, Pod의 디스크 볼륨으로 마운트 시키는 방법도 있다.

앞의 cm-file configMap을 /tmp/config/에 마운트 해보도록 하자.

아래와 같이 Deployment 스크립트를 작성한다.


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: cm-file-deployment-vol

spec:

 replicas: 3

 minReadySeconds: 5

 selector:

   matchLabels:

     app: cm-file-vol

 template:

   metadata:

     name: cm-file-vol-pod

     labels:

       app: cm-file-vol

   spec:

     containers:

     - name: cm-file-vol

       image: gcr.io/terrycho-sandbox/cm-file-volume:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

       volumeMounts:

         - name: config-profile

           mountPath: /tmp/config

     volumes:

       - name: config-profile

         configMap:

           name: cm-file


configMap을 디스크 볼륨으로 마운트해서 사용하는 방법은 volumes 을 configMap으로 정의하면 된다. 위의 예제에서 처럼 volume을 정의할때, configMap으로 정의하고 configMap의 이름을 cm-file로 정의하여, cm-file configMap을 선택하였다. 이 볼륨을 volumeMounts를 이용해서 /tmp/config에 마운트 되도록 하였다.

이때 중요한점은 마운트 포인트에 마운트 될때, 파일명을 configMap내의 키가 파일명이 된다.


다음 테스트를 위해서 server.js 애플리케이션에 /tmp/config/profile.properties 파일을 읽어서 출력하도록 아래와 같이 코드를 작성한다.

var os = require('os');

var fs = require('fs');


var http = require('http');

var handleRequest = function(request, response) {

 fs.readFile('/tmp/config/profile.properties',function(err,data){

   response.writeHead(200);

   response.end("Read configMap from file  "+data+" \n");

 });


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 server.js를 도커로 패키징해서 배포한후, service를 붙여서 테스트해보면 다음과 같은 결과를 얻을 수 있다.



파일 내용이 출력되는 것을 확인할 수 있다

디스크에 마운트가 제대로 되었는지를 확인하기 위해서 Pod에 쉘로 로그인해서 확인해보자


그림과 같이 /tmp/config/profile.properties 파일이 생성된것을 확인할 수 있다.



쿠버네티스 #8

Ingress


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



쿠버네티스의 서비스는, L4 레이어로 TCP 단에서 Pod들을 밸런싱한다.

서비스의 경우에는 TLS (SSL)이나, VirtualHost와 같이 여러 호스트명을 사용하거나 호스트명에 대한 라우팅이 불가능하고, URL Path에 따른 서비스간 라우팅이 불가능하다.

또한 마이크로 서비스 아키텍쳐 (MSA)의 경우에는 쿠버네티스의 서비스 하나가 MSA의 서비스로 표현되는 경우가 많고 서비스는 하나의 URL로 대표 되는 경우가 많다. (/users, /products, …)

그래서 MSA 서비스간의 라우팅을 하기 위해서는 API 게이트웨이를 넣는 경우가 많은데, 이 경우에는 API 게이트웨이에 대한 관리포인트가 생기기 때문에, URL 기반의 라우팅 정도라면, API 게이트웨이 처럼 무거운 아키텍쳐 컴포넌트가 아니라, L7 로드밸런서 정도로 위의 기능을 모두 제공이 가능하다.


쿠버네티스에서 HTTP(S)기반의 L7 로드밸런싱 기능을 제공하는 컴포넌트를 Ingress라고 한다.

개념을 도식화 해보면 아래와 같은데, Ingress 가 서비스 앞에서 L7 로드밸런서 역할을 하고, URL에 따라서 라우팅을 하게 된다.


Ingress 가 서비스 앞에 붙어서, URL이 /users와 /products 인것을 각각 다른 서비스로 라우팅 해주는 구조가 된다.


Ingress 은 여러가지 구현체가 존재한다.

구글 클라우드의 경우에는 글로벌 로드 밸런서(https://github.com/kubernetes/ingress-gce/blob/master/README.md) 를 Ingress로 사용이 가능하며, 오픈소스 구현체로는 nginx (https://github.com/kubernetes/ingress-nginx/blob/master/README.md)  기반의 ingress 구현체가 있다.  상용 제품으로는 F5 BIG IP Controller (http://clouddocs.f5.com/products/connectors/k8s-bigip-ctlr/v1.5/) 가 현재 사용이 가능하고, 재미있는 제품으로는 오픈소스 API 게이트웨이 솔루션인 Kong (https://konghq.com/blog/kubernetes-ingress-controller-for-kong/)이 Ingress 컨트롤러의 기능을 지원한다.

각 구현체마다 설정 방법이 다소 차이가 있으며, 특히 Ingress 기능은 베타 상태이기 때문에, 향후 변경이 있을 수 있음을 감안하여 사용하자

URL Path 기반의 라우팅

이 글에서는 구글 클라우드 플랫폼의 로드밸런서를 Ingress로 사용하는 것을 예를 들어 설명한다.

위의 그림과 같이 users 와 products 서비스 두개를 구현하여 배포하고, 이를 ingress를 이용하여 URI가  /users/* 와 /products/* 를 각각의 서비스로 라우팅 하는 방법을 구현해보도록 하겠다.


node.js와 users와 products 서비스를 구현한다.

서비스는 앞에서 계속 사용해왔던 간단한 HelloWorld 서비스를 약간 변형해서 사용하였다.


아래는 users 서비스의 server.js 코드로 “Hello World! I’m User server ..”를 HTTP 응답으로 출력하도록 하였다.  Products 서비스는 User server를 product 서버로 문자열만 변경하였다.


var os = require('os');


var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm User server "+os.hostname() +" \n");


 //log

 console.log("["+

Date(Date.now()).toLocaleString()+

"] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


다음으로 서비스를 배포해야 하는데, Ingress를 사용하려면 서비스는 Load Balancer 타입이 아니라, NodePort 타입으로 배포해야 한다.  다음은 user 서비스를 nodeport 서비스로 배포하는 yaml 스크립트이다. (Pod를 컨트롤하는 Deployment 스크립트는 생략하였다.)


users-svc-nodeport.yaml

apiVersion: v1

kind: Service

metadata:

 name: users-node-svc

spec:

 selector:

   app: users

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 

같은 방식으로, Product 서비스도 아래와 같이 NodePort로 배포한다.

product-svc-nodeport.yaml

apiVersion: v1

kind: Service

metadata:

 name: products-node-svc

spec:

 selector:

   app: products

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

     

이때 별도로 nodeport를 지정해주지 않았는데, 자동으로 쿠버네티스 클러스터가 nodeport를 지정해준다.

아래와 같이 products-node-svc와 users-node-svc가 각각 배포된것을 확인할 수 있고, ClusterIP의 포트는 80, NodePort는 각각 31442, 32220으로 배포된것을 확인할 수 있다.




다음 Ingress를 생성해보자. 다음은 hello-ingress 라는 이름으로 위에서 만든 두개의 서비스를 라우팅해주는 서비스를 생성하기 위한 yaml  파일이다.

hello-ingress.yaml

apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress

spec:

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


spec 부분에, rules.http.paths 부분에, 라우팅할 path와 서비스를 정의해준다.

User 서비스는 /users/* URI인 경우 라우팅하게 하고, 앞에서 만든 users-node-svc로 라우팅하도록 한다. 이때 servicePort는 ClusterIP의 service port를 지정한다. (Google Cloud HTTP Load balancer를 이용하는 Ingress의 경우에는  실질적으로는 nodeport로 통신을 하지만 별도로 지정하지 않고 ingress가 자동으로 해당 서비스의 nodeport를 찾아서 맵핑이 된다. )
(참고 : https://kubernetes.io/docs/concepts/services-networking/ingress/

Lines 12-14: A backend is a service:port combination as described in the services doc. Ingress traffic is typically sent directly to the endpoints matching a backend.)


%kubectl create -f hello-ingress.yaml

을 실행하면 ingress가 생성이 되고 kubectl get ing 명령어를 이용하면 생성된 ingress를 확인할 수 있다.



Ingress 가 생성된 후, 실제로 사용이 가능하기까지는 약 1~2분의 시간이 소요된다. 물리적으로 HTTP 로드밸런서를 생성하고, 이 로드밸런서가 서비스가 배포되어 있는 노드에 대한 HealthCheck를 완료하고 문제가 없으면 서비스를 제공하는데, HealthCheck 주기가 1분이기 때문에, 1~2분 정도를 기다려 주는게 좋다. 그전까지는 404 에러나 500 에러가 날것이다.


준비가 끝난후, curl 명령을 이용해서 ingress의 URL에 /users/ 와 /products를 각각 호출해보면 각각, users를 서비스 하는 서버와, products를 서비스 하는 서버로 라우팅이 되서 각각 다른 메세지가 출력되는 것을 확인할 수 있다.




그러면 내부적으로 클라우드 내에서 Ingress를 위한 인프라가 어떻게 생성되었는지 확인해보자

구글 클라우드 콘솔에서 아래와 같이 Network services > Load balancing 메뉴로 들어가보자



아래와 같이 HTTP 로드밸런서가 생성이 된것을 확인할 수 있다.


이름을 보면 k8s는 쿠버네티스용 로드밸런서임을 뜻하고, 중간에 default는 네임 스페이스를 의미한다. 그리고 ingress의 이름인 hello-ingress로 생성이 되어 있다.

로드밸런서를 클릭해서 디테일을 들어가 보면 아래와 같은 정보를 확인할 수 있다.




3개의 백엔드 (인스턴스 그룹)이 맵핑되었으며, /users/*용, /products/*용 그리고, 디폴트용이 생성되었다.

모든 트래픽이 쿠버네티스 클러스터 노드로 동일하게 들어가기 때문에, Instance group의 이름을 보면 모두 동일한것을 확인할 수 있다. 단, 중간에 Named Port 부분을 보면 포트가 다른것을 볼 수 있는데, 31442, 32220 포트를 사용하고 있고, 앞에서 users, produtcs 서비스를 nodeport로 생성하였을때, 자동으로 할당된 nodeport이다.


개념적으로 다음과 같은 구조가 된다.


(편의상 디폴트 백앤드의 라우팅은 표현에서 제외하였다.)


Ingress에 접속되는 서비스를 LoadBalancer나 ClusterIP타입이 아닌 NodePort 타입을 사용하는 이유는, Ingress로 사용되는 구글 클라우드 로드밸런서에서, 각 서비스에 대한 Hearbeat 체크를 하기 위해서인데, Ingress로 배포된 구글 클라우드 로드밸런서는 각 노드에 대해서 nodeport로 Heartbeat 체크를 해서 문제 있는 노드를 로드밸런서에서 자동으로 제거나 복구가되었을때는 자동으로 추가한다.

Static IP 지정하기

서비스와 마찬가지로 Ingress 역시 Static IP를 지정할 수 있다.

서비스와 마찬가지로, static IP를 gcloud 명령을 이용해서 생성한다. 이때 IP를 regional로 생성할 수 도 있지만, ingress의 경우에는 global IP를 사용할 수 있다. --global 옵션을 주면되는데, global IP의 경우에는 regional IP와는 다르게 구글 클라우드의 망 가속 기능을 이용하기 때문에, 구글 클라우드의 100+ 의 Pop (Point of Presence)를 이용하여 가속이 된다.


조금 더 깊게 설명을 하면, 일반적으로 한국에서 미국으로 트래픽을 보낼 경우 한국 → 인터넷 → 미국 식으로 트래픽이 가는데 반해 global IP를 이용하면, 한국에서 가장 가까운 Pop (일본)으로 접속되고, Pop으로 부터는 구글 클라우드의 전용 네트워크를 이용해서 구글 데이타 센터까지 연결 (한국 → 인터넷 → 일본 Pop → 미국 ) 이 되기 때문에 일반 인터넷으로 연결하는 것 대비에서 빠른 성능을 낼 수 있다.


아래와 같이 gcloud 명령을 이용하여, global IP를 생성한다.




구글 클라우드 콘솔에서, 정적 IP를 확인해보면 아래와 같이 hello-ingress-ip 와 같이 IP가 생성되어 등록되어 있는 것을 확인할 수 있다.



Static IP를 이용해서 hello-ingress-staticip 이름으로 ingress를 만들어보자

다음과 같이 hello-ingress-staticip.yaml 파일을 생성한다.


apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress-staticip

 annotations:

   kubernetes.io/ingress.global-static-ip-name: "hello-ingress-ip"

spec:

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


이 파일을 이용하여, ingress를 생성한 후에, ingress ip를 확인하고 curl 을 이용해서 결과를 확인하면 다음과 같다.


Ingress with TLS

이번에는 Ingress 로드밸런서를 HTTP가 아닌 HTTPS로 생성해보겠다.


SSL 인증서 생성

SSL을 사용하기 위해서는 SSL 인증서를 생성해야 한다. openssl (https://www.openssl.org/)툴을 이용하여 인증서를 생성해보도록 한다.


인증서 생성에 사용할 키를 생성한다.

%openssl genrsa -out hello-ingress.key 2048

명령으로 키를 생성하면 hello-ingress.key라는 이름으로 Private Key 파일이 생성된다.




다음 SSL 인증서를 생성하기 위해서, 인증서 신청서를 생성한다.인증서 신청서 생성시에는 앞에서 생성한 Private Key를 사용한다.

다음 명령어를 실행해서 인증서 신청서 생성을 한다.

%openssl req -new -key hello-ingress.key -out hello-ingress.csr

이때 인증서 내용에 들어갈 국가, 회사 정보, 연락처등을 아래와 같이 입력한다.


인증서 신청서가 hello-ingress.csr 파일로 생성이 되었다. 그러면 이 신청서를 이용하여, SSL 인증서를 생성하자. 테스트이기 때문에 공인 인증 기관에 신청하지 않고, 간단하게 사설 인증서를 생성하도록 하겠다.


다음 명령어를 이용하여 hello-ingress.crt라는 이름으로 SSL 인증서를 생성한다.

%openssl x509 -req -day 265 -in hello-ingress.csr -signkey hello-ingress.key -out hello-ingress.crt



설정하기

SSL 인증서 생성이 완료되었으면, 이 인증서를 이용하여 SSL을 지원하는 ingress를 생성해본다.

SSL 인증을 위해서는 앞서 생성한 인증서와 Private Key 파일이 필요한데, Ingress는 이 파일을 쿠버네티스의 secret 을 이용하여 읽어드린다.


Private Key와 SSL 인증서를 저장할 secret를 생성해보자 앞에서 생성한 hello-ingress.key와 hello-ingress.crt 파일이 ./ssl_cert 디렉토리에 있다고 하자


다음과 같이 kubectl create secret tls 명령을 이용해서 hello-ingress-secret 이란 이름의 secret을 생성한다.

%kubectl create secret tls hello-ingress-serect --key ./ssl_cert/hello-ingress.key --cert ./ssl_cert/hello-ingress.crt


명령을 이용하여 secret을 생성하면, key 이라는 이름으로 hello-ingress.key 파일이 바이너리 형태로 secret에 저장되고 마찬가지로 cert라는 이름으로 hello-ingress.crt 가 저장된다.


생성된 secret을 확인하기 위해서

%kubectl describe secret hello-ingress-secret

명령을 실행해보면 아래와 같이 tls.key 와 tls.crt 항목이 각각 생성된것을 확인할 수 있다.


다음 SSL을 지원하는 ingress를 생성해야 한다.

앞에서 생성한 HTTP ingress와 설정이 다르지 않으나 spec 부분에 tls라는 항목에 SSL 인증서와 Private Key를 저장한 secret 이름을 secretName이라는 항목으로 넘겨줘야 한다.


hello-ingress-tls.yaml


apiVersion: extensions/v1beta1

kind: Ingress

metadata:

 name: hello-ingress-tls

spec:

 tls:

 - secretName: hello-ingress-secret

 rules:

 - http:

     paths:

     - path: /users/*

       backend:

         serviceName: users-node-svc

         servicePort: 80

     - path: /products/*

       backend:

         serviceName: products-node-svc

         servicePort: 80


이 파일을 이용해서 TLS ingress를 생성한 후에, IP를 조회해보자

아래와 같이 35.241.6.159 IP에 hello-ingress-tls 이름으로 ingress가 된것을 확인할 수 있고 포트는 HTTP 포트인 80 포트 이외에, HTTPS포트인 443 포트를 사용하는 것을 볼 수 있다.



다음 HTTPS로 테스트를 해보면 다음과 같이 HTTPS로 접속이 되는 것을 확인할 수 있다.



사설 인증서이기 때문에 위처럼 Not Secure라는 메세지가 뜬다. 인증서 정보를 확인해보면 아래와 같이 앞서 생성한 인증서에 대한 정보가 들어가 있는 것을 확인할 수 있다.



쿠버네티스 #7

서비스 (service)


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


Service

쿠버네티스 서비스에 대해서 자세하게 살펴보도록 한다.

Pod의 경우에 지정되는 Ip가 랜덤하게 지정이 되고 리스타트 때마다 변하기 때문에 고정된 엔드포인트로 호출이 어렵다, 또한 여러 Pod에 같은 애플리케이션을 운용할 경우 이 Pod 간의 로드밸런싱을 지원해줘야 하는데, 서비스가 이러한 역할을 한다.

서비스는 지정된 IP로 생성이 가능하고, 여러 Pod를 묶어서 로드 밸런싱이 가능하며, 고유한 DNS 이름을 가질 수 있다.


서비스는 다음과 같이 구성이 가능하며, 라벨 셀렉터 (label selector)를 이용하여, 관리하고자 하는 Pod 들을 정의할 수 있다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

멀티 포트 지원

서비스는 동시에 하나의 포트 뿐 아니라 여러개의 포트를 동시에 지원할 수 있다. 예를 들어 웹서버의 HTTP와 HTTPS 포트가 대표적인 예인데,  아래와 같이 ports 부분에 두개의 포트 정보를 정의해주면 된다.

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

   - name: https

     port: 443

     protocol: TCP

     targetPort: 8082

 type: LoadBalancer

로드 밸런싱 알고리즘

서비스가 Pod들에 부하를 분산할때 디폴트 알고리즘은 Pod 간에 랜덤으로 부하를 분산하도록 한다.

만약에 특정 클라이언트가 특정 Pod로 지속적으로 연결이 되게 하려면  Session Affinity를 사용하면 되는데, 서비스의 spec 부분에 sessionAffinity: ClientIP로 주면 된다.




웹에서 HTTP Session을 사용하는 경우와 같이 각 서버에 각 클라이언트의 상태정보가 저장되어 있는 경우에 유용하게 사용할 수 있다.

Service Type

서비스는 IP 주소 할당 방식과 연동 서비스등에 따라 크게 4가지로 구별할 수 있다.

  • Cluster IP

  • Load Balancer

  • Node IP

  • External name


ClusterIP

디폴트 설정으로, 서비스에 클러스터 IP (내부 IP)를 할당한다. 쿠버네티스 클러스터 내에서는 이 서비스에 접근이 가능하지만, 클러스터 외부에서는 외부 IP 를 할당  받지 못했기 때문에, 접근이 불가능하다.

Load Balancer

보통 클라우드 벤더에서 제공하는 설정 방식으로, 외부 IP 를 가지고 있는 로드밸런서를 할당한다. 외부 IP를 가지고 있기  때문에, 클러스터 외부에서 접근이 가능하다.

NodePort

클러스터 IP로만 접근이 가능한것이 아니라, 모든 노드의 IP와 포트를 통해서도 접근이 가능하게 된다. 예를 들어 아래와 같이 hello-node-svc 라는 서비스를 NodePort 타입으로 선언을 하고, nodePort를 30036으로 설정하면, 아래 설정에 따라 클러스터 IP의  80포트로도 접근이 가능하지만, 모든 노드의 30036 포트로도 서비스를 접근할 수 있다.


hello-node-svc-nodeport.yaml


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 type: NodePort

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

     nodePort: 30036


아래 그림과 같은 구조가 된다.




이를 간단하게 테스트 해보자.

아래는 구글 클라우드에서 쿠버네티스 테스트 환경에서 노드로 사용되고 있는 3개의 VM 목록과 IP 주소이다.


현재 노드는 아래와 같이 3개의 노드가 배포되어 있고 IP 는 10.146.0.8~10이다.

내부 IP이기 때문에, VPC 내의 내부 IP를 가지고 있는 서버에서 테스트를 해야 한다.


같은 내부 IP를 가지고 있는 envoy-ubuntu 라는 머신 (10.146.0.18)에서 각 노드의 30036 포트로 curl을 테스트해본 결과 아래와 같이 모든 노드의 IP를 통해서 서비스 접근이 가능한것을 확인할 수 있다.



ExternalName

ExternalName은 외부 서비스를 쿠버네티스 내부에서 호출하고자할때 사용할 수 있다.

쿠버네티스 클러스터내의 Pod들은 클러스터 IP를 가지고 있기 때문에 클러스터 IP 대역 밖의 서비스를 호출하고자 하면, NAT 설정등 복잡한 설정이 필요하다.

특히 AWS 나 GCP와 같은 클라우드 환경을 사용할 경우 데이타 베이스나, 또는 클라우드에서 제공되는 매지니드 서비스 (RDS, CloudSQL)등을 사용하고자할 경우에는 쿠버네티스 클러스터 밖이기 때문에, 호출이 어려운 경우가 있는데, 이를 쉽게 해결할 수 있는 방법이 ExternalName 타입이다.

아래와 같이 서비스를 ExternalName 타입으로 설정하고, 주소를 DNS로  my.database.example.com으로 설정해주면 이 my-service는 들어오는 모든 요청을 my.database.example.com 으로 포워딩 해준다. (일종의 프록시와 같은 역할)

kind: Service
apiVersion: v1
metadata:
 name: my-service
 namespace: prod
spec:
 type: ExternalName
 externalName: my.database.example.com

다음과 같은 구조로 서비스가 배포된다.



DNS가 아닌 직접 IP를 이용하는 방식

위의 경우 DNS를 이용하였는데, DNS가 아니라 직접 IP 주소를 이용하는 방법도 있다.

서비스 ClusterIP 서비스로 생성을 한 후에, 이 때 서비스에 속해있는 Pod를 지정하지 않는다.

apiVersion: v1

kind: Service

metadata:

 name: external-svc-nginx

spec:

 ports:

 - port: 80



다음으로, 아래와 같이 서비스의 EndPoint를 별도로 지정해주면 된다.

apiVersion: v1

kind: Endpoints

metadata:

 name: external-svc-nginx

subsets:

 - addresses:

   - ip: 35.225.75.124

   ports:

   - port: 80


이 때 서비스명과 서비스 EndPoints의 이름이 동일해야 한다. 위의 경우에는 external-svc-nginx로 같은 서비스명을 사용하였고 이 서비스는 35.225.75.124:80 서비스를 가르키도록 되어 있다.

그림으로 구조를 표현해보면 다음과 같다.




35.225.75.124:80 은 nginx 웹서버가 떠 있는 외부 서비스이고, 아래와 같이 간단한 문자열을 리턴하도록 되어 있다.



이를 쿠버네티스 내부 클러스터의 Pod 에서 curl 명령을 이용해서 호출해보면 다음과 같이 외부 서비스를 호출할 수 있음을 확인할 수 있다.

Headless Service

서비스는 접근을 위해서 Cluster IP 또는 External IP 를 지정받는다.

즉 서비스를 통해서 제공되는 기능들에 대한 엔드포인트를 쿠버네티스 서비스를 통해서 통제하는 개념인데, 마이크로 서비스 아키텍쳐에서는 기능 컴포넌트에 대한 엔드포인트 (IP 주소)를 찾는 기능을 서비스 디스커버리 (Service Discovery) 라고 하고, 서비스의 위치를 등록해놓는 서비스 디스커버리 솔루션을 제공한다. Etcd 나 hashcorp의 consul (https://www.consul.io/)과 같은 솔루션이 대표적인 사례인데, 이 경우 쿠버네티스 서비스를 통해서 마이크로 서비스 컴포넌트를 관리하는 것이 아니라, 서비스 디스커버리 솔루션을 이용하기 때문에, 서비스에 대한 IP 주소가 필요없다.

이런 시나리오를 지원하기 위한 쿠버네티스의 서비스를 헤드리스 서비스 (Headless service) 라고 하는데, 이러한 헤드리스 서비스는 Cluster IP등의 주소를 가지지 않는다. 단 DNS이름을 가지게 되는데, 이 DNS 이름을 lookup 해보면, 서비스 (로드밸런서)의 IP 를 리턴하지 않고, 이 서비스에 연결된 Pod 들의 IP 주소들을 리턴하게 된다.


간단한 테스트를 해보면


와 같이 기동중인 Pod들이 있을때, Pod의 IP를 조회해보면 다음과 같다.


10.20.0.25,10.20.0.22,10.20.0.29,10.20.0.26 4개가 되는데,

다음 스크립트를 이용해서 hello-node-svc-headless 라는 헤드리스 서비스를 만들어보자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc-headless

spec:

 clusterIP: None

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080


아래와 같이 ClusterIP가 할당되지 않음을 확인할 수 있다.



다음 쿠버네티스 클러스터내의 다른 Pod에서 nslookup으로 해당 서비스의 dns 이름을 조회해보면 다음과 같이 서비스에 의해 제공되는 pod 들의 IP 주소 목록이 나오는 것을 확인할 수 있다.




Service discovery

그러면 생성된 서비스의 IP를 어떻게 알 수 있을까? 서비스가 생성된 후 kubectl get svc를 이용하면 생성된 서비스와 IP를 받아올 수 있지만, 이는 서비스가 생성된 후이고, 계속해서 변경되는 임시 IP이다.

DNS를 이용하는 방법

가장 쉬운 방법으로는 DNS 이름을 사용하는 방법이 있다.

서비스는 생성되면 [서비스 명].[네임스페이스명].svc.cluster.local 이라는 DNS 명으로 쿠버네티스 내부 DNS에 등록이 된다. 쿠버네티스 클러스터 내부에서는 이 DNS 명으로 서비스에 접근이 가능한데, 이때 DNS에서 리턴해주는 IP는 외부 IP (External IP)가 아니라 Cluster IP (내부 IP)이다.


아래 간단한 테스트를 살펴보자. hello-node-svc 가 생성이 되었는데, 클러스터내의 pod 중 하나에서 ping으로 hello-node-svc.default.svc.cluster.local 을 테스트 하니, hello-node-svc의 클러스터 IP인 10.23.241.62가 리턴되는 것을 확인할 수 있다.



External IP (외부 IP)

다른 방식으로는 외부 IP를 명시적으로 지정하는 방식이 있다. 쿠버네티스 클러스터에서는 이 외부 IP를 별도로 관리하지 않기 때문에, 이 IP는 외부에서 명시적으로 관리되어야 한다.


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 externalIPs:

 - 80.11.12.11

 

외부 IP는 Service의 spec 부분에서 externalIPs 부분에 IP 주소를 지정해주면 된다.

구글 클라우드의 경우

퍼블릭 클라우드 (AWS, GCP 등)의 경우에는 이 방식 보다는 클라우드내의 로드밸런서를 붙이는 방법을 사용한다.


구글 클라우드의 경우를 살펴보자.서비스에 정적인 IP를 지정하기 위해서는 정적 IP를 생성해야 한다. 구글 클라우드 콘솔내의 VPC 메뉴의 External IP 메뉴에서 생성해도 되고, 아래와 같이 gcloud CLI 명령어를 이용해서 생성해도 된다.


IP를 생성하는 명령어는 gcloud compute addresses create [IP 리소스명] --region [리전]

을 사용하면 된다. 구글 클라우드의 경우에는 특정 리전만 사용할 수 있는 리저널 IP와, 글로벌에 모두 사용할 있는 IP가 있는데, 서비스에서는 리저널 IP만 사용이 가능하다. (글로벌 IP는 후에 설명하는 Ingress에서 사용이 가능하다.)

아래와 같이

%gcloud compute addresses create hello-node-ip-region  --region asia-northeast1

명령어를 이용해서 asia-northeast1 리전 (일본)에 hello-node-ip-region 이라는 이름으로 Ip를 생성하였다. 생성된 IP는 describe 명령을 이용해서 확인할 수 있으며, 아래 35.200.64.17 이 배정된것을 확인할 수 있다.



이 IP는 서비스가 삭제되더라도 계속 유지되고, 다시 재 사용이 가능하다.

그러면 생성된 IP를 service에 적용해보자

다음과 같이 hello-node-svc-lb-externalip.yaml  파일을 생성하자


apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer

 loadBalancerIP: 35.200.64.17


타입을 LoadBalancer로 하고, loadBalancerIP 부분에 앞에서 생성한 35.200.64.17 IP를 할당한다.

다음 이 파일을 kubectl create -f hello-node-svc-lb-externalip.yaml 명령을 이용해서 생성하면, hello-node-svc 가 생성이 되고, 아래와 같이 External IP가 우리가 앞에서 지정한 35.200.64.17 이 지정된것을 확인할 수 있다.




쿠버네티스 #6

Replication Controller를 이용하여 서비스 배포하기

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


1. 도커 파일 만들기

node.js로 간단한 웹서버를 만들어서 도커로 패키징 해보자.

실습을 진행하기 위해서 로컬 환경에 도커와, node.js 가 설치되어 있어야 한다. 이 두 부분은 생략하도록 한다.

여기서 사용한 실습 환경은 node.js carbon 버전 (8.11.3), 도커 맥용 18.05.0-ce, build f150324 을 사용하였다.

node.js 애플리케이션 준비하기

node.js로 간단한 웹 애플리케이션을 제작해보자 server.js라는 이름으로 아래 코드를 작성한다.

var os = require('os');

 

var http = require('http');

var handleRequest = function(request, response) {

 response.writeHead(200);

 response.end("Hello World! I'm "+os.hostname());

 

 //log

 console.log("["+

               Date(Date.now()).toLocaleString()+

               "] "+os.hostname());

}

var www = http.createServer(handleRequest);

www.listen(8080);


이 코드는 8080 포트로 웹서버를 띄워서 접속하면 “Hello World!” 문자열과 함께, 서버의 호스트명을 출력해준다. 그리고 stdout에 로그로, 시간과 서버의 호스트명을 출력해준다.

코드 작성이 끝났으면, 서버를 실행해보자

%node server.js


다음 브라우저로 접속하면 다음과 같은 결과를 얻을 수 있다.


그리고 콘솔화면에는 아래와 같이 시간과 호스트명이 로그로 함께 출력된다.

도커로 패키징하기

그러면 이 node.js 애플리케이션을 도커 컨테이너로 패키징 해보자

Dockerfile 이라는 파일을 만들고 아래 코드를 작성한다.

FROM node:carbon

EXPOSE 8080

COPY server.js .

CMD node server.js > log.out


이 코드는 node.js carborn (8.11.3) 컨테이너 이미지를 베이스로 한후에,  앞서 작성한 server.js 코드를 복사한후에, node server.js > log.out 명령어를 실행하도록 하는 컨테이너를 만드는 설정파일이다.

설정 파일이 준비되었으면,  도커 컨테이너 파일을 만들어보자


% docker build -t gcr.io/terrycho-sandbox/hello-node:v1 .


docker build  명령은 컨테이너를 만드는 명령이고, -t는 빌드될 이미지에 대한 태그를 정하는 명령이다.

빌드된 컨테이너 이미지는 gcr.io/terrycho-sandbox/hello-node로  태깅되는데, 이는 향후에 구글 클라우드 컨테이너 레지스트리에 올리기 위해서 태그 명을 구글 클라우드 컨테이너 레지스트리의 포맷을 따른 것이다. (참고 https://cloud.google.com/container-registry/docs/pushing-and-pulling)

포맷은 [HOST_NAME]/[GOOGLE PROJECT-ID]/[IMAGE NAME]


gcr.io/terrycho-sandbox는 도커 이미지가 저장될 리파지토리의 경로를 위의 규칙에 따라 정의한 것인데,

  • gcr.io는 구글 클라우드 컨테이너 리파지토리 US 리전을 지칭하며,

  • terrycho-sandbox는 본인의 구글 프로젝트 ID를 나타낸다.

  • 이미지명을 hello-node 로 지정하였다.

  • 마지막으로 콜론(:) 으로 구별되어 정의한 부분은 태그 부분으로, 여기서는 “v1”으로 태깅을 하였다.


이미지는 위의 이름으로 지정하여 생성되어 로컬에 저장된다.




빌드를 실행하면 위와 같이 node:carbon 이미지를 읽어와서 필요한 server.js 파일을 복사하고 컨테이너 이미지를 생성한다.

컨테이너 이미지가 생성되었으면 로컬 환경에서 이미지를 기동 시켜보자


%docker run -d -p 8080:8080 gcr.io/terrycho-sandbox/hello-node:v1


명령어로 컨테이너를 실행할 수 있다.

  • -d 옵션은 컨테이너를 실행하되, 백그라운드 모드로 실행하도록 하였다.

  • -p는 포트 맵핑으로 뒤의 포트가 도커 컨테이너에서 돌고 있는 포트이고, 앞의 포트가 이를 밖으로 노출 시키는 포트이다 예를 들어 -p 9090:8080 이면 컨테이너의 8080포트를 9090으로 노출 시켜서 서비스 한다는 뜻이다. 여기서는 컨테이너 포트와 서비스로 노출 되는 포트를 동일하게 8080으로 사용하였다.


컨테이너를 실행한 후에, docker ps 명령어를 이용하여 확인해보면 아래와 같이 hello-node:v1 이미지로 컨테이너가 기동중인것을 확인할 수 있다.



다음 브라우져를 통해서 접속을 확인하기 위해서 localhost:8080으로 접속해보면 아래와 같이 Hello World 와 호스트명이 출력되는 것을 확인할 수 있다.


로그가 제대로 출력되는지 확인하기 위해서 컨테이너 이미지에 쉘로 접속해보자

접속하는 방법은


% docker exec -i -t [컨테이너 ID] /bin/bash

를 실행하면 된다. 컨테이너 ID 는 앞의 docker ps 명령을 이용하여 기동중인 컨테이너 명을 보면 처음 부분이 컨테이너 ID이다.

hostname 명령을 실행하여 호스트명을 확인해보면 위에 웹 브라우져에서 출력된 41a293ba79a7과 동일한것을 확인할 수 있다. 디렉토리에는 server.js 파일이 복사되어 있고, log.out 파일이 생성된것을 볼 수 있다.  

cat log.out을 이용해서 보면, 시간과 호스트명이 로그로 출력된것을 확인할 수 있다.



2. 쿠버네티스 클러스터 준비

구글 클라우드 계정 준비하기

구글 클라우드 계정 생성은 http://bcho.tistory.com/1107 문서를 참고하기 바란다.

쿠버네티스 클러스터 생성하기

쿠버네티스 클러스터를 생성해보자, 클러스터 생성은 구글 클라우드 콘솔의 Kubernetes Engine > Clusters 메뉴에서 Create 를 선택하면 클러스터 생성이 가능하다.



클러스터 이름을 넣어야 하는데, 여기서는 terry-gke-10 을 선택하였다. 구글 클라우드에서 쿠버네티스 클러스터는 싱글 존에만 사용가능한 Zonal 클러스터와 여러존에 노드를 분산 배포하는 Regional 클러스터 두 가지가 있는데, 여기서는 하나의 존만 사용하는 Zonal 클러스터를 설정한다. (Regional은 차후에 다루도록 하겠다.)

다음 클러스터를 배포한 존을 선택하는데, asia-northeast1-c (일본)을 선택하였다.

Cluster Version은 쿠버네티스 버전인데, 1.10.2 버전을 선택한다.

그리고 Machine type은 쿠버네티스 클러스터의 노드 머신 타입인데, 간단한 테스트 환경이기 때문에,  2 CPU에 7.5 메모리를 지정하였다.

다음으로 Node Image는 노드에 사용할 OS 이미지를 선택하는데, Container Optimized OS를 선택한다. 이 이미지는 컨테이너(도커)를 운영하기 위해 최적화된 이미지이다.

다음으로는 노드의 수를 Size에서 선택한다. 여기서는 3개의 노드를 운용하도록 설정하였다.


아래 부분에 보면  Automatic node upgrades 라는 기능이 있다.


구글 클라우드의 재미있는 기능중 하나인데, 쿠버네티스 버전이 올라가면 자동으로 버전을 업그레이드 해주는 기능으로, 이 업그레이드는 무정지로 진행 된다.


gcloud 와 kubectl 설치하기

클러스터 설정이 끝났으면 gloud (Google Cloud SDK 이하 gcloud)를 인스톨한다.

gcloud 명령어의 인스톨 방법은 OS마다 다른데, https://cloud.google.com/sdk/docs/quickstarts 문서를 참고하면 된다.

별다른 어려운 작업은 없고, 설치 파일을 다운 받아서 압축을 푼후에, 인스톨 스크립트를 실행하면 된다.


kubectl은 쿠버네티스의 CLI (Command Line Interface)로, gcloud를 인스톨한후에,

%gcloud components install kubectl

명령을 이용하면 인스톨할 수 있다.

쿠버네티스 클러스터 인증 정보 얻기

gcloud와 kubectl 명령을 설치하였으면, 이 명령어들을 사용할때 마다 쿠버네티스에 대한 인증이 필요한데, 인증에 필요한 인증 정보는 아래 명령어를 이용하면, 자동으로 사용이 된다.

gcloud container clusters get-credentials CLUSTER_NAME

여기서는 클러스터명이 terry-gke10이기 때문에,

%gcloud container clusters get-credentials terry-gke-10

을 실행한다.


명령어 설정이 끝났으면, gcloud 명령이 제대로 작동하는지를 확인하기 위해서, 현재 구글 클라우드내에 생성된 클러스터 목록을 읽어오는 gcloud container clusters list 명령어를 실행해보자



위와 같이 terry-gke-10 이름으로 asia-northeast1-c 존에 쿠버네티스 1.10.2-gke.3 버전으로 클러스터가 생성이 된것을 볼 수 있고, 노드는 총 3개의 실행중인것을 확인할 수 있다.

3. 쿠버네티스에 배포하기

이제 구글 클라우드에 쿠버네티스 클러스터를 생성하였고, 사용을 하기 위한 준비가 되었다.

앞에서 만든 도커 이미지를 패키징 하여, 이 쿠버네티스 클러스터에 배포해보도록 하자.

여기서는 도커 이미지를 구글 클라우드내의 도커 컨테이너 레지스트리에 등록한 후, 이 이미지를 이용하여 ReplicationController를 통해 총 3개의 Pod를 구성하고 서비스를 만들어서 이 Pod들을 외부 IP를 이용하여 서비스를 제공할 것이다.

도커 컨테이너 이미지 등록하기

먼저 앞에서 만든 도커 이미지를 구글 클라우드 컨테이너 레지스트리(Google Container Registry 이하 GCR) 에 등록해보자.

GCR은 구글 클라우드에서 제공하는 컨테이너 이미지 저장 서비스로, 저장 뿐만 아니라, CI/CD 도구와 연동하여, 자동으로 컨테이너 이미지를 빌드하는 기능, 그리고 등록되는 컨테이너 이미지에 대해서 보안적인 문제가 있는지 보안 결함을 스캔해주는 기능과 같은 다양한 기능을 제공한다.


컨테이너 이미지를 로컬환경에서 도커 컨테이너 저장소에 저장하려면 docker push라는 명령을 사용하는데, 여기서는 GCR을 컨테이너 이미지 저장소로 사용할 것이기 때문에, GCR에 대한 인증이 필요하다.

인증은 한번만 해놓으면 되는데

%gcloud auth configure-docker

명령을 이용하면, 인증 정보가 로컬 환경에 자동으로 저장된다.



인증이 완료되었으면, docker push 명령을 이용하여 이미지를 GCR에 저장한다.

%docker push gcr.io/terrycho-sandbox/hello-node:v1


명령어를 실행하면, GCR에 hello-node 이미지가 v1 태그로 저장된다.


이미지가 GCR에 잘 저장되었는지를 확인하기 위해서 구글 클라우드 콘솔에 Container Registry (GCR)메뉴에서 Images라는 메뉴를 들어가보자




아래와 같이 hello-node 폴더에 v1이라는 태그로 이미지가 등록된것을 확인할 수 있다.

ReplicationController 등록

컨테이너 이미지가 등록되었으면 이 이미지를 이용해서 Pod를 생성해보자,  Pod 생성은 Replication Controller (이하 rc)를 생성하여, rc가 Pod 생성 및 컨트롤을 하도록 한다.


다음은 rc 생성을 위한 hello-node-rc.yaml 파일이다.


apiVersion: v1

kind: ReplicationController

metadata:

 name: hello-node-rc

spec:

 replicas: 3

 selector:

   app: hello-node

 template:

   metadata:

     name: hello-node-pod

     labels:

       app: hello-node

   spec:

     containers:

     - name: hello-node

       image: gcr.io/terrycho-sandbox/hello-node:v1

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


hello-node-rc 라는 이름으로 rc를 생성하는데, replica 를 3으로 하여, 총 3개의 pod를 생성하도록 한다.

템플릿 부분에 컨테이너 스팩에 컨테이너 이름은 hello-node로 하고 이미지는 앞서 업로드한 gcr.io/terrycho-sandbox/hello-node:v1 를 이용해서 컨테이너를 만들도록 한다. 컨테이너의 포트는 8080을 오픈한다. 템플릿 부분에서 app 이라는 이름의 라벨을 생성하고 그 값을 hello-node로 지정하였다. 이 라벨은 나중에 서비스 (service)에 의해 외부로 서비스될 pod들을 선택하는데 사용 된다.


여기서 imagePullPolicy:Always  라고 설정한 부분이 있는데, 이는 Pod를 만들때 마다 매번 컨테이너 이미지를 확인해서 새 이미지를 사용하도록 하는 설정이다.  컨테이너 이미지는 한번 다운로드가 되면 노드(Node) 에 저장이 되어 있게 되고, 사용이 되지 않는 이미지 중에 오래된 이미지는 Kublet이 가비지 컬렉션 (Garbage collection) 정책에 따라 이미지를 삭제하게 되는데, 문제는 노드에 이미 다운되어 있는 이미지가 있을 경우 컨테이너 생성시 노드에 이미 다운로드 되어 있는 이미지를 사용한다. 컨테이너 리파지토리에 같은 이름으로 이미지를 업데이트 하거나 심지어 그 이미지를 삭제하더라도 노드에 이미지가 이미 다운로드 되어 있으면 다운로드된 이미지를 사용하기 때문에, 업데이트 부분이 반영이 안된다.

이를 방지하기 위해서 imagePullPolicy:Always로 해주면 컨테이너 생성시마다 이미지 리파지토리를 검사해서 새 이미지를 가지고 오기 때문에, 업데이트된 내용을 제대로 반영할 수 있다.


%kubectl create -f hello-node-rc.yaml


명령어를 실행해서 rc와 pod를 생성한다.




위의 그림과 같이 3개의 Pod가 생성된것을 확인할 수 있는데, Pod가 제대로 생성되었는지 확인하기 위해서 hello-node-rc-rsdzl pod에서 hello-node-rc-2phgg pod의 node.js 웹서버에 접속을 해볼 것이다.

아직 서비스를 붙이지 않았기 때문에, 이 pod들은 외부 ip를 이용해서 서비스가 불가능하기 때문에, 쿠버네티스 클러스터 내부의 pod를 이용하여 내부 ip (private ip)간에 통신을 해보기 위해서 pod에서 pod를 호출 하는 것이다. kubectl describe pod  [pod 명] 명령을 이용하면, 해당 pod의 정보를 볼 수 있다. hello-node-rc-2hpgg pod의 cluster ip (내부 ip)를 확인해보면 10.20.1.27 인것을 확인할 수 있다.


kubectl exec 명령을 이용하면 쉘 명령어를 실행할 수 있는데, 다음과 같이 hello-node-rc-rsdzl pod에서 첫번째 pod인 hello-node-rc-2phgg의 ip인 10.20.1.27의 8080 포트로 curl 을 이용해 HTTP 요청을 보내보면 다음과 같이 정상적으로 응답이 오는 것을 볼 수 있다.


Service 등록

rc와 pod 생성이 끝났으면 이제 서비스를 생성해서 pod들을 외부 ip로 서비스 해보자

다음은 서비스를 정의한 hello-node-svc.yaml 파일이다.


hello-node-svc.yaml

apiVersion: v1

kind: Service

metadata:

 name: hello-node-svc

spec:

 selector:

   app: hello-node

 ports:

   - port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


Selector 부분에 app:hello-node 로 지정하여, pod들 중에 라벨의 키가 app이고 값이 hello-node인 pod 들만 서비스에 연결하도록 지정하였다. 다음 서비스의 포트는 80으로 지정하였고, pod의 port는 8080으로 지정하였다.


서비스가 배포되면 위와 같은 구조가 된다.

%kubectl create -f hello-node-svc.yaml

명령을 이용하면 서비스가 생성이 된다.


다음 생성된 서비스의 외부 ip를 얻기 위해서 kubectl get svc 명령을 실행해보자

아래 그림과 같이 35.200.40.161 IP가 할당된것을 확인할 수 있다.


이 IP로 접속을 해보면 아래와 같이 정상적으로 응답이 오는 것을 확인할 수 있다.


RC 테스트

rc는 pod의 상태를 체크하다가 문제가 있으면 다시, pod를 기동해주는 기능을 한다.

이를 테스트하기 위해서 강제적으로 모든 pod를 제거해보자. kubectl delete pod --all을 이용하면 모든 pod를 제거할 수 있는데, 아래 그림을 보면, 모든 pod를 제거했더니 3개의 pod가 제거되고 새롭게 3개의 pod가 기동되는 것을 확인할 수 있다.



운영중에 탄력적으로 pod의 개수를 조정할 수 있는데, kubectl scale 명령을 이용하면 된다.

kubectl scale --replicas=[pod의 수] rc/[rc 명] 식으로 사용하면 된다. 아래는 pod의 수를 4개로 재 조정한 내용이다.



자원 정리

테스트가 끝났으면 서비스, rc,pod를 삭제해보자.

  • 서비스 삭제는 kubectl delete svc --all 명령어를 이용한다.

  • rc 삭제는 kubectl delete rc --all

  • pod 삭제는 kubectl delete pod --all

을 사용한다.

삭제시 주의할점은 pod를 삭제하기 전에 먼저 rc를 삭제해야 한다. 아니면, pod가 삭제된 후 rc에 의해서 다시 새로운 pod가 생성될 수 있다.


쿠버네티스 #4

Volume (디스크)

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


이번 글에서는 쿠버네티스의 디스크 서비스인 볼륨에 대해서 알아보도록 하겠다.

쿠버네티스에서 볼륨이란 Pod에 종속되는 디스크이다. (컨테이너 단위가 아님). Pod 단위이기 때문에, 그 Pod에 속해 있는 여러개의 컨테이너가 공유해서 사용될 수 있다.

볼륨 종류

쿠버네티스의 볼륨은 여러가지 종류가 있는데,  로컬 디스크 뿐 아니라, NFS, iSCSI, Fiber Channel과 같은 일반적인 외장 디스크 인터페이스는 물론, GlusterFS나, Ceph와 같은 오픈 소스 파일 시스템, AWS EBS, GCP Persistent 디스크와 같은 퍼블릭 클라우드에서 제공되는 디스크, VsphereVolume과 같이 프라이비트 클라우드 솔루션에서 제공하는 디스크 볼륨까지 다양한 볼륨을 지원한다.

자세한 볼륨 리스트는 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes 를 참고하기 바란다.


이 볼륨 타입을 구별해보면 크게 임시 디스크, 로컬 디스크 그리고 네트워크 디스크 등으로 분류할 수 있다.


Temp

Local

Network

emptyDir

hostPath

GlusterFS

gitRepo

NFS

iSCSI

gcePersistentDisk

AWS EBS

azureDisk

Fiber Channel

Secret

VshereVolume


그럼 각각에 대해서 알아보도록 하자

emptyDir

emptyDir은 Pod가 생성될때 생성되고, Pod가 삭제 될때 같이 삭제되는 임시 볼륨이다.

단 Pod 내의 컨테이너 크래쉬되어 삭제되거나 재시작 되더라도 emptyDir의 생명주기는 컨테이너 단위가 아니라, Pod 단위이기 때문에, emptyDir은 삭제 되지 않고 계속해서 사용이 가능하다.

생성 당시에는 디스크에 아무 내용이 없기 때문에, emptyDir  이라고 한다.

emptyDir의 물리적으로 노드에서 할당해주는 디스크에 저장이 되는데, (각 환경에 따라 다르다. 노드의 로컬 디스크가 될 수 도 있고, 네트워크 디스크등이 될 수 도 있다.) emptyDir.medium 필드에 “Memory”라고 지정해주면, emptyDir의 내용은 물리 디스크 대신 메모리에 저장이 된다.


다음은 하나의 Pod에 nginx와 redis 컨테이너를 기동 시키고, emptyDir 볼륨을 생성하여 이를 공유하는 설정이다.


apiVersion: v1

kind: Pod

metadata:

 name: shared-volumes

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: shared-storage

     mountPath: /data/shared

 - name: nginx

   image: nginx

   volumeMounts:

   - name: shared-storage

     mountPath: /data/shared

 volumes:

 - name : shared-storage

   emptyDir: {}


shared-storage라는 이름으로 emptyDir 기반의 볼륨을 만든 후에, nginx와 redis 컨테이너의 /data/shared 디렉토리에 마운트를 하였다.


Pod를 기동 시킨후에, redis 컨테이너의 /data/shared 디렉토리에 들어가 보면 당연히 아무 파일도 없는 것을 확인할 수 있다.

이 상태에서 아래와 같이 file.txt 파일을 생성하였다.



다음 nginx 컨테이너로 들어가서 /data/shared 디렉토리를 살펴보면 file.txt 파일이 있는 것을 확인할 수 있다.



이 파일은 redis 컨테이너에서 생성이 되어 있지만, 같은 Pod 내이기 때문에, nginx 컨테이너에서도 접근이 가능하게 된다.

hostPath

다음은 hostPath 라는 볼륨 타입인데, hostPath는 노드의 로컬 디스크의 경로를 Pod에서 마운트해서 사용한다. 같은 hostPath에 있는 볼륨은 여러 Pod 사이에서 공유되어 사용된다.

또한  Pod가 삭제 되더라도 hostPath에 있는 파일들은 삭제되지 않고 다른 Pod가 같은 hostPath를 마운트하게 되면, 남아 있는 파일을 액세스할 수 있다.


주의할점 중의 하나는 Pod가 재시작되서 다른 노드에서 기동될 경우, 그 노드의 hostPath를 사용하기 때문에, 이전에 다른 노드에서 사용한 hostPath의 파일 내용은 액세스가 불가능하다.


hostPath는 노드의 파일 시스템을 접근하는데 유용한데, 예를 들어 노드의 로그 파일을 읽어서 수집하는 로그 에이전트를 Pod로 배포하였을 경우, 이 Pod에서 노드의 파일 시스템을 접근해야 한다. 이러한 경우에 유용하게 사용할 수 있다.


아래는 노드의 /tmp 디렉토리를 hostPath를 이용하여 /data/shared 디렉토리에 마운트 하여 사용하는 예제이다.


apiVersion: v1

kind: Pod

metadata:

 name: hostpath

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data/shared

 volumes:

 - name : terrypath

   hostPath:

     path: /tmp

     type: Directory



이 Pod를 배포해서 Pod를 Id를 얻어보았다.


Pod Id를 통해서 VM을 아래와 같이 확인하였다.


VM에 SSH로 접속해서 /tmp/에 hello.txt 파일을 생성하였다.




다음, Pod의 컨테이너에서 마운트된 /data/shared 디렉토리를 확인해보면 아래와 같이 노드의 /tmp 디렉토리의 내용이 그대로 보이는 것을 볼 수 있다.


gitRepo

볼륨 타입중에 gitRepo라는 유용한 볼륨 타입이 하나 있어서 소개한다.

이 볼륨은 생성시에 지정된 git 리파지토리의 특정 리비전의 내용을 clone을 이용해서 내려 받은후에 디스크 볼륨을 생성하는 방식이다. 물리적으로는 emptyDir이 생성되고, git 레파지토리 내용을 clone으로 다운 받는다.




HTML과 같은 정적 파일이나 Ruby on rails, PHP, node.js 와 같은 스크립트 언어 기반의 코드들은 gitRepo 볼륨을 이용하여 손쉽게 배포할 수 있다.


apiVersion: v1

kind: Pod

metadata:

name: gitrepo-volume-pod

spec:

containers:

- image: nginx:alpine

  name: web-server

  volumeMounts:

  - name: html

    mountPath: /usr/share/nginx/html

    readOnly: true

  ports:

  - containerPort: 80

    protocol: TCP

volumes:

- name: html

  gitRepo:

       repository: https://github.com/luksa/kubia-website-example.git

       revision: master

       directory: .


이 설정은 https://github.com/luksa/kubia-website-example.git 의 master 리비전을 클론으로 다운받아서 /usr/share/nginx/html에 마운트 시키는 설정이다.


PersistentVolume and PersistentVolumeClaim

일반적으로 디스크 볼륨을 설정하려면 물리적 디스크를 생성해야 하고, 이러한 물리적 디스크에 대한 설정을 자세하게 이해할 필요가 있다.

쿠버네티스는 인프라에 대한 복잡성을 추상화를 통해서 간단하게 하고, 개발자들이 손쉽게 필요한 인프라 (컨테이너,디스크, 네트워크)를 설정할 수 있도록 하는 개념을 가지고 있다

그래서 인프라에 종속적인 부분은 시스템 관리자가 설정하도록 하고, 개발자는 이에 대한 이해 없이 간단하게 사용할 수 있도록 디스크 볼륨 부분에 PersistentVolumeClaim (이하 PVC)와 PersistentVolume (이하 PV)라는 개념을 도입하였다.


시스템 관리자가 실제 물리 디스크를 생성한 후에, 이 디스크를 PersistentVolume이라는 이름으로 쿠버네티스에 등록한다.

개발자는 Pod를 생성할때, 볼륨을 정의하고, 이 볼륨 정의 부분에 물리적 디스크에 대한 특성을 정의하는 것이 아니라 PVC를 지정하여, 관리자가 생성한 PV와 연결한다.


그림으로 정리해보면 다음과 같다.


시스템 관리자가 생성한 물리 디스크를 쿠버네티스 클러스터에 표현한것이 PV이고, Pod의 볼륨과 이 PV를 연결하는 관계가 PVC가 된다.


이때 주의할점은 볼륨은 생성된후에, 직접 삭제하지 않으면 삭제되지 않는다. PV의 생명 주기는 쿠버네티스 클러스터에 의해서 관리되면 Pod의 생성 또는 삭제에 상관없이 별도로 관리 된다. (Pod와 상관없이 직접 생성하고 삭제해야 한다.)

PersistentVolume

PV는 물리 디스크를 쿠버네티스에 정의한 예제로, NFS 파일 시스템 5G를 pv0003이라는 이름으로 정의하였다.




PV를 설정하는데 여러가지 설정 옵션이 있는데, 간략하게 그 내용을 살펴보면 다음과 같다.

  • Capacity
    볼륨의 용량을 정의한다. 현재는 storage 항목을 통해서 용량만을 지정하는데 향후에는 필요한 IOPS나 Throughput등을 지원할 예정이다.

  • VolumeMode
    VolumeMode는 Filesystem (default)또는 raw를 설정할 수 있는데, 볼륨이 일반 파일 시스템인데, raw 볼륨인지를 정의한다.

  • Reclaim Policy
    PV는 연결된 PVC가 삭제된 후 다시 다른 PVC에 의해서 재 사용이 가능한데, 재 사용시에 디스크의 내용을 지울지 유지할지에 대한 정책을 Reclaim Policy를 이용하여 설정이 가능하다.

    • Retain : 삭제하지 않고 PV의 내용을 유지한다.

    • Recycle : 재 사용이 가능하며, 재 사용시에는 데이타의 내용을 자동으로 rm -rf 로 삭제한 후 재사용이 된다.

    • Delete : 볼륨의 사용이 끝나면, 해당 볼륨은 삭제 된다. AWS EBS, GCE PD,Azure Disk등이 이에 해당한다.

Reclaim Policy은 모든 디스크에 적용이 가능한것이 아니라, 디스크의 특성에 따라서 적용이 가능한 Policy가 있고, 적용이 불가능한 Policy 가 있다.

  • AccessMode
    AccessMode는 PV에 대한 동시에 Pod에서 접근할 수 있는 정책을 정의한다.

    • ReadWriteOnce (RWO)
      해당 PV는 하나의 Pod에만 마운트되고 하나의 Pod에서만 읽고 쓰기가 가능하다.

    • ReadOnlyMany(ROX)
      여러개의 Pod에 마운트가 가능하며, 여러개의 Pod에서 동시에 읽기가 가능하다. 쓰기는 불가능하다.

    • ReadWriteMany(RWX)
      여러개의 Pod에 마운트가 가능하고, 동시에 여러개의 Pod에서 읽기와 쓰기가 가능하다.

위와 같이 여러개의 모드가 있지만, 모든 디스크에 사용이 가능한것은 아니고 디스크의 특성에 따라서 선택적으로 지원된다.


PV의 라이프싸이클

PV는 생성이 되면, Available 상태가 된다. 이 상태에서 PVC에 바인딩이 되면 Bound 상태로 바뀌고 사용이 되며, 바인딩된 PVC가 삭제 되면, PV가 삭제되는 것이 아니라  Released 상태가 된다. (Available이 아니면 사용은 불가능하고 보관 상태가 된다.)

PV 생성 (Provisioning)

PV의 생성은 앞에서 봤던것 처럼 yaml 파일등을 이용하여, 수동으로 생성을 할 수 도 있지만, 설정에 따라서 필요시마다 자동으로 생성할 수 있게 할 수 있다. 이를 Dynamic Provisioning (동적 생성)이라고 하는데, 이에 대해서는 PVC를 설명하면서 같이 설명하도록 하겠다.

PersistentVolumeClaim

PVC는 Pod의 볼륨과 PVC를 연결(바인딩/Bind)하는 관계 선언이다.

아래 예제를 보자 아래 예제는 PVC의 예제이다.



(출처 : https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims)


  • accessMode, VolumeMode는 PV와 동일하다.

  • resources는 PV와 같이, 필요한 볼륨의 사이즈를 정의한다.

  • selector를 통해서 볼륨을 선택할 수 있는데, label selector 방식으로 이미 생성되어 있는 PV 중에, label이 매칭되는 볼륨을 찾아서 연결하게 된다.


PV/PVC 예제

그러면 예제를 통해서 PV를 생성하고, 이 PV를 PVC에 연결한후에, PVC를 Pod에 할당하여 사용하는 방법을 살펴보도록 하자. 예제는 구글 클라우드 환경을 사용하였다.

1.물리 디스크 생성

먼저 구글 클라우드 콘솔에서 Compute Engine 부분에서 아래와 같이 Disks 부분에서 물리 디스크를 생성한다.


디스크를 pv-demo-disk라는 이름으로 생성하였다.

이때 주의할점은 디스크의 region과 zone이 쿠베네티스 클러스터가 배포된 region과 zone에 동일해야 한다.


2.생성된 디스크로 PV를 선언

생성된 디스크를 이용하여 PV를 생성한다. 아래는 PV를 생성하기 위한 yaml 파일이다.


existing-pd.yaml

apiVersion: v1

kind: PersistentVolume

metadata:

 name: pv-demo

spec:

 storageClassName:

 capacity:

   storage: 20G

 accessModes:

   - ReadWriteOnce

 gcePersistentDisk:

   pdName: pv-demo-disk

   fsType: ext4


PV의이름은 pv-demo이고, gcePersistentDisk에서 앞에서 생성한 pv-demo-disk 를 사용하도록 정의하였다.

파일을 실행하면, 아래와 같이 pv-demo로 PV가 생성된것을 확인할 수 있다.

3. 다음 PVC를 생성한다.

아래는 앞에서 생성한 pv-demo PV를 사용하는 PVC를 생성하는 yaml 파일이다. 하나의 Pod에서만 액세스가 가능하도록 accessMode를 ReadWriteOnce로 설정하였다.


existing-pvc.yaml

apiVersion: v1

kind : PersistentVolumeClaim

metadata:

 name: pv-claim-demo

spec:

 storageClassName: ""

 volumeName: pv-demo

 accessModes:

   - ReadWriteOnce

 resources:

   requests:

     storage: 20G


4. Pod를 생성하여, PVC를 바인딩

그러면 앞에서 생성한 PV와 PVC를 Pod에 생성해서 연결하자


existing-pod-redis.yaml

apiVersion: v1

kind: Pod

metadata:

 name: redis

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data

 volumes:

 - name : terrypath

   persistentVolumeClaim:

     claimName: pv-claim-demo


앞에서 생성한 PVC pv-claim-demo를 Volume에 연결한후, 이 볼륨을 /data 디렉토리에 마운트 하였다.

Pod를 생성한후에, 생성된 Pod에 df -k 로 디스크 연결 상태를 확인해 보면 다음과 같다.



/dev/sdb 가 20G로 생성되어 /data 디렉토리에 마운트 된것을 확인할 수 있다.

Dynamic Provisioning

앞에서 본것과 같이 PV를 수동으로 생성한후 PVC에 바인딩 한 후에, Pod에서 사용할 수 있지만, 쿠버네티스 1.6에서 부터 Dynamic Provisioning (동적 생성) 기능을 지원한다. 이 동적 생성 기능은 시스템 관리자가 별도로 디스크를 생성하고 PV를 생성할 필요 없이 PVC만 정의하면 이에 맞는 물리 디스크 생성 및 PV 생성을 자동화해주는 기능이다.




PVC를 정의하면, PVC의 내용에 따라서 쿠버네티스 클러스터가 물리 Disk를 생성하고, 이에 연결된 PV를 생성한다.

실 환경에서는 성능에 따라 다양한 디스크(nVME, SSD, HDD, NFS 등)를 사용할 수 있다. 그래서 디스크를 생성할때, 필요한 디스크의 타입을 정의할 수 있는데, 이를 storageClass 라고 하고, PVC에서 storage class를 지정하면, 이에 맞는 디스크를 생성하도록 한다.

Storage class를 지정하지 않으면, 디폴트로 설정된 storage class 값을 사용하게 된다.


동적 생성 방법은 어렵지 않다. PVC에 필요한 디스크 용량을 지정해놓으면, 자동으로 이에 해당하는 물리 디스크 및 PV가 생성이 된다. 아래는 동적으로 PV를 생성하는 PVC 예제이다.


dynamic-pvc.yaml

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

 name: mydisk

spec:

 accessModes:

   - ReadWriteOnce

 resources:

   requests:

     storage: 30Gi


다음 Pod를 생성한다.

apiVersion: v1

kind: Pod

metadata:

 name: redis

spec:

 containers:

 - name: redis

   image: redis

   volumeMounts:

   - name: terrypath

     mountPath: /data/shared

 volumes:

 - name : terrypath

   persistentVolumeClaim:

     claimName: mydisk


Pod를 생성한후에, kubectl get pvc 명령어를 이용하여, 생성된 PVC와 PV를 확인할 수 있다.

PVC는 위에서 정의한것과 같이 mydisk라는 이름으로 생성되었고, Volume (PV)는 pvc-4a…. 식으로 새롭게 생성되었다.

Storage class

스토리지 클래스를 살펴보자,

아래는  AWS EBS 디스크에 대한 스토리지 클래스를 지정한 예로, slow 라는 이름으로 스토리지 클래스를 지정하였다. EBS 타입은 io1을 사용하고, GB당 IOPS는 10을 할당하도록 하였고, 존은 us-east-1d와 us-east-1c에 디스크를 생성하도록 하였다.



아래는 구글 클라우드의 Persistent Disk (pd)의 예로, slow라는 이름으로 스토리지 클래스를 지정하고, pd-standard (HDD)타입으로 디스크를 생성하되 us-central1-a와 us-central1-b 존에 디스크를 생성하도록 하였다.



이렇게 정의한 스토리지 클래스는  PVC 정의시에, storageClassName에 적으면 PVC에 연결이 되고, 스토리지 클래스에 정해진 스펙에 따라서 물리 디스크와 PV를 생성하게 된다.

쿠버네티스 #3

개념이해 (2/2) : 고급 컨트롤러


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



고급 컨트롤러

RC,RS,Deployment는 웹서버와 같은 일반적인 워크로드에 대해 Pod를 관리하기 위한 컨트롤러이다. 실제 운영환경에서는 웹서버와 같은 일반적인 워크로드 이외에,  데이타베이스,배치 작업, 데몬 서버와 같이 다양한 형태의 워크로드 모델이 존재하는데 이를 지원하기 위해서 쿠버네티스는 다양한 컨트롤러를 제공함으로써, Pod의 운영을 다양한 시나리오에 맞게 지원하고 있다.

DaemonSet

DaemonSet (이하 DS) 은 Pod가 각각의 노드에서 하나씩만 돌게 하는 형태로 Pod를 관리하는 컨트롤러이다. 아래 그림을 보자


RC나 RS에 의해서 관리되는 Pod 는 여러 노드의 상황에 따라서 일반적으로 비균등적으로 배포가 되지만,  DS에 의해 관리되는 Pod는 모든 노드에 균등하게 하나씩만 배포 된다.

이런 형태의 워크로드는 서버의 모니터링이나 로그 수집 용도로 많이 사용되는데, DS의 다른 특징중 하나는, 특정 Node들에만 Pod가 하나씩만 배포 되도록 설정이 가능하다.

앞에서 언급한 로그나 모니터링 시나리오에서 특정 장비에 대한 모니터링을 하고자 할 때 이런 시나리오가 유효하다. 예를 들어 특정 장비(노드)에만 Nvme SSD를 사용하거나 GPU를 사용할 경우에는 그 장비가 설치된 노드만을 모니터링하면 된다.



DS는 특정 노드에만 Pod를 배포할 수 있도록 , Pod의 “node selector”를 이용해서 라벨을 이용하여 특정 노드만을 선택할 수 있게 지원한다.

Job

워크로드 모델중에서 배치나 한번 실행되고 끝나는 형태의 작업이 있을 수 있다.

예를 들어 원타임으로 파일 변환 작업을 하거나, 또는 주기적으로 ETL 배치 작업을 하는 경우에는 웹서버 처럼 계속 Pod가 떠 있을 필요없이 작업을 할때만 Pod 를 띄우면 된다.

이러한 형태의 워크로드 모델을 지원하는 컨트롤러를 Job이라고 한다.


Job에 의해서 관리되는 Pod는 Job이 종료되면, Pod 를 같이 종료한다.

Job을 정의할때는 보통 아래와 같이 컨테이너 스펙 부분에 image 뿐만 아니라, 컨테이너에서 Job을 수행하기 위한 커맨드(command) 를 같이 입력한다.


apiVersion: batch/v1
kind: Job
metadata:
 name: pi
spec:
 template:
   spec:
     containers:
     - name: pi
       image: perl
       command: ["perl",  "-Mbignum=bpi", "-wle", "print bpi(2000)"]
     restartPolicy: Never
 backoffLimit: 4



Job 컨트롤러에 의해서 실행된 Pod 는 이 command의 실행 결과에 따라서 Job이 실패한지 성공한지를 판단한다. (프로세스의 exit 코드로 판단한다.)  Job이 종료되었는데, 결과가 실패라면,이 Job을 재 실행할지 또는 그냥 끝낼지를 설정에 따라서 결정한다.


Job이 끝나기 전에 만약에 비정상적으로 종료된다면 어떻게 될것인가?

아래 그림을 보자 쿠버네티스 클러스터에서 특정 노드가 장애가 났다고 가정하자, RC/RS에 의해서 관리되고 있는 Pod 는 자동으로 다른 노드에서 다시 자동으로 생성되서 시작될것이고, 컨트롤러에 의해 관리되고 있지 않은 Pod 는 다시 다른 노드에서 기동되지 않고 사라질것이다.

그렇다면 Job 에 의해서 관리되는 Pod는 어떻게 될것인가?



두가지 방법으로 설정할 수 있는데, 장애시 다시 시작하게 하거나 또는 장애시 다시 시작하지 않게 할 수 있다.

다시 시작의 개념은 작업의 상태가 보장되는것이 아니라, 다시 처음부터 작업이 재 시작되는 것이기 때문에 resume이 아닌 restart의 개념임을 잘 알아야하고, 다시 시작 처음부터 작업을 시작하더라도 데이타가 겹치거나 문제가 없는 형태라야 한다.


배치 작업의 경우 작업을 한번만 실행할 수 도 있지만, 같은 작업을 연속해서 여러번 수행하는 경우가 있다. (데이타가 클 경우 범위를 나눠서 작업하는 경우) 이런 경우를 위해서 Job 컨트롤러는 같은 Pod를 순차적으로, 여러번 실행할 수 있도록 설정이 가능하다. Job 설정에서 completion에 횟수를 주면, 같은 작업을 completion 횟수만큼 순차적으로 반복한다.


만약에 여러 작업을 처리해야 하지만 순차성이 필요없고 병렬로 처리를 하고 싶다면, Job설정에서 parallelism 에 동시 실행할 수 있는 Pod의 수를 주면, 지정된 수 만큼 Pod를 실행하여 completion 횟수를 병렬로 처리한다. 아래 그림은 completion이 5, parallelism이 2일때, 하나의 노드에서 모든 Pod가 실행된다고 가정했을때, 실행 순서를 보여주는 그림이다.



Cron jobs

Job 컨트롤러에 의해서 실행되는 배치성 작업들에 대해서 고려할 점중 하나는 이런 배치성 작업을 메뉴얼로 실행하는 것이 아니라, 주기적으로 자동화해서 실행할 필요가 있는데, 이렇게 주기적으로 정해진 스케쥴에 따라 Job 컨트롤러에 의해 작업을 실행해주는 컨트롤러로 cron jobs 컨트롤러가 있다.

cron jobs 컨트롤러는 Unix cron 명령어처럼, 시간에 따른 실행조건을 정의해놓을 수 있고, 이에 따라 Job 컨트롤러를 실행하여, 정의된 Pod를 실행할 수 있게 한다.


아래는 cron jobs 컨트롤러의 예제인데, job 컨트롤러와 설정이 다르지 않다.


apiVersion: batch/v1beta1
kind: CronJob
metadata:
 name: hello
spec:
 schedule: "*/1 * * * *"
 jobTemplate:
   spec:
     template:
       spec:
         containers:
         - name: hello
           image: busybox
           args:
           - /bin/sh
           - -c
           - date; echo Hello from the Kubernetes cluster
         restartPolicy: OnFailure



다른 점은 CronJob 스펙 설정 부분에 “schedule”이라는 항목이 있고 반복 조건을 unix cron과 같이 설정하면 된다.

StatefulSet

마지막으로, 1.9에 정식으로 릴리즈된 StatefulSet이 있다.

RS/RC나 다른 컨트롤러로는 데이타베이스와 같이 상태를 가지는 애플리케이션을 관리하기가 어렵다.

그래서 이렇게 데이타 베이스등과 같이 상태를 가지고 있는 Pod를 지원하기 위해서 StatefulSet 이라는 것이 새로 소개되었는데, 이를 이해하기 위해서는 쿠버네티스의 디스크 볼륨에 대한 이해가 필요하기 때문에 다음에 볼륨과 함께 다시 설명하도록 한다.


2회에 걸쳐서 쿠버네티스의 컴포넌트 개념들에 대해서 살펴보았고, 다음글에서는 쿠버네티스의 아키텍쳐에 대해서 간략하게 살펴보도록 하겠다.

쿠버네티스 #2

개념 이해 (1/2)


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


쿠버네티스를 공부하면서 가장 헷갈리는 부분이 용어와 컨셉이다. 이 컨셉만 잘 이해하면 쿠버네티스를 쉽게 이해하고 사용할 수 있지만, 적어도 내 기준에서는 문서들의 용어나 개념 설명이 다소 어려웠다.

쿠버네티스의 개념은 크게 오브젝트 두개의 개념에서 출발한다. 각각을 살펴보도록 하자

마스터와 노드

쿠버네티스를 이해하기 위해서는 먼저 클러스터의 구조를 이해할 필요가 있는데, 구조는 매우 간단하다. 클러스터 전체를 관리하는 컨트롤러로써 마스터가 존재하고, 컨테이너가 배포되는 머신 (가상머신이나 물리적인 서버머신)인 노드가 존재한다.


오브젝트

쿠버네티스를 이해하기 위해서 가장 중요한 부분이 오브젝트이다. 가장 기본적인 구성단위가 되는 기본 오브젝트(Basic object)와, 이 기본 오브젝트(Basic object) 를 생성하고 관리하는 추가적인 기능을 가진 컨트롤러(Controller) 로 이루어진다. 그리고 이러한 오브젝트의 스펙(설정)이외에 추가정보인 메타 정보들로 구성이 된다고 보면 된다.

오브젝트 스펙 (Object Spec)

오브젝트들은 모두 오브젝트의 특성 (설정정보)을 기술한 오브젝트 스펙 (Object Spec)으로 정의가 되고, 커맨드 라인을 통해서 오브젝트 생성시 인자로 전달하여 정의를 하거나 또는 yaml이나 json 파일로 스펙을 정의할 수 있다.

기본 오브젝트 (Basic Object)

쿠버네티스에 의해서 배포 및 관리되는 가장 기본적인 오브젝트는 컨테이너화되어 배포되는 애플리케이션의 워크로드를 기술하는 오브젝트로 Pod,Service,Volume,Namespace 4가지가 있다.


간단하게 설명 하자면 Pod는 컨테이너화된 애플리케이션, Volume은 디스크, Service는 로드밸런서 그리고 Namespace는 패키지명 정도로 생각하면 된다. 그러면 각각을 자세하게 살펴보도록 하자.

Pod

Pod 는 쿠버네티스에서 가장 기본적인 배포 단위로, 컨테이너를 포함하는 단위이다.

쿠버네티스의 특징중의 하나는 컨테이너를 개별적으로 하나씩 배포하는 것이 아니라 Pod 라는 단위로 배포하는데, Pod는 하나 이상의 컨테이너를 포함한다.


아래는 간단한 Pod를 정의한 오브젝트 스펙이다. 하나하나 살펴보면


apiVersion: v1

kind: Pod

metadata:

 name: nginx

spec:

 containers:

 - name: nginx

   image: nginx:1.7.9

   ports:

   - containerPort: 8090


  • apiVersion은 이 스크립트를 실행하기 위한 쿠버네티스 API 버전이다 보통 v1을 사용한다.

  • kind 에는 리소스의 종류를 정의하는데, Pod를 정의하려고 하기 때문에, Pod라고 넣는다.

  • metadata에는 이 리소스의 각종 메타 데이타를 넣는데, 라벨(뒤에서 설명할)이나 리소스의 이름등 각종 메타데이타를 넣는다

  • spec 부분에 리소스에 대한 상세한 스펙을 정의한다.

    • Pod는 컨테이너를 가지고 있기 때문에, container 를 정의한다. 이름은 nginx로 하고 도커 이미지 nginx:1.7.9 를 사용하고, 컨테이너 포트 8090을 오픈한다.


Pod 안에 한개 이상의 컨테이너를 가지고 있을 수 있다고 했는데 왜 개별적으로 하나씩 컨테이너를 배포하지 않고 여러개의 컨테이너를 Pod 단위로 묶어서 배포하는 것인가?


Pod는 다음과 같이 매우 재미있는 특징을 갖는다.


  • Pod 내의 컨테이너는 IP와 Port를 공유한다.
    두 개의 컨테이너가 하나의 Pod를 통해서 배포되었을때, localhost를 통해서 통신이 가능하다.
    예를 들어 컨테이너 A가 8080, 컨테이너 B가 7001로 배포가 되었을 때, B에서 A를 호출할때는 localhost:8080 으로 호출하면 되고, 반대로 A에서 B를 호출할때에넌 localhost:7001로 호출하면 된다.

  • Pod 내에 배포된 컨테이너간에는 디스크 볼륨을 공유할 수 있다.
    근래 애플리케이션들은 실행할때 애플리케이션만 올라가는것이 아니라 Reverse proxy, 로그 수집기등 다양한 주변 솔루션이 같이 배포 되는 경우가 많고, 특히 로그 수집기의 경우에는 애플리케이션 로그 파일을 읽어서 수집한다. 애플리케이션 (Tomcat, node.js)와 로그 수집기를 다른 컨테이너로 배포할 경우, 일반적인 경우에는 컨테이너에 의해서 파일 시스템이 분리되기 때문에, 로그 수집기가 애플리케이션이 배포된 컨테이너의 로그파일을 읽는 것이 불가능 하지만, 쿠버네티스의 경우 하나의 Pod 내에서는 컨테이너들끼리 볼륨을 공유할 수 있기 때문에 다른 컨테이너의 파일을 읽어올 수 있다.


위와 같이 애플리케이션과 애플리케이션에서 사용하는 주변 프로그램을 같이 배포하는 패턴을 마이크로 서비스 아키텍쳐에서 사이드카 패턴(Side car pattern)이라고 하는데, 이 외에도 Ambassador, Adapter Container 등 다양한 패턴이 있는데, 이는 나중에 다른 글에서 상세하게 설명하도록 한다.

Volume

Pod가 기동할때 디폴트로, 컨테이너마다 로컬 디스크를 생성해서 기동되는데, 이 로컬 디스크의 경우에는 영구적이지 못하다. 즉 컨테이너가 리스타트 되거나 새로 배포될때 마다 로컬 디스크는 Pod 설정에 따라서 새롭게 정의되서 배포되기 때문에, 디스크에 기록된 내용이 유실된다.

데이타 베이스와 같이 영구적으로 파일을 저장해야 하는 경우에는 컨테이너 리스타트에 상관 없이 파일을 영속적으로 저장애햐 하는데, 이러한 형태의 스토리지를 볼륨이라고 한다.

볼륨은 컨테이너의 외장 디스크로 생각하면 된다. Pod가 기동할때 컨테이너에 마운트해서 사용한다.


앞에서 언급한것과 같이 쿠버네티스의 볼륨은 Pod내의 컨테이너간의 공유가 가능하다.


웹 서버를 배포하는 Pod가 있을때, 웹서비스를 서비스하는 Web server 컨테이너, 그리고 컨텐츠의 내용 (/htdocs)를 업데이트하고 관리하는 Content mgmt 컨테이너, 그리고 로그 메세지를 관리하는 Logger라는 컨테이너이가 있다고 하자

  • WebServer 컨테이너는 htdocs 디렉토리의 컨테이너를 서비스하고, /logs 디렉토리에 웹 억세스 기록을 기록한다.

  • Content 컨테이너는 htdocs 디렉토리의 컨텐트를 업데이트하고 관리한다.

  • Logger 컨테이너는 logs 디렉토리의 로그를 수집한다.

이 경우 htdocs 컨텐츠 디렉토리는 WebServer와 Content 컨테이너가 공유해야 하고 logs 디렉토리는 Webserver 와 Logger 컨테이너가 공유해야 한다. 이러한 시나리오에서 볼륨을 사용할 수 있다.


아래와 같이 htdocs와 logs 볼륨을 각각 생성한 후에, htdocs는 WebServer와, Contents management 컨테이너에 마운트 해서 공유하고, logs볼륨은 Logger와 WebServer 컨테이너에서 공유하도록 하면된다.  



쿠버네티스는 다양한 외장 디스크를 추상화된 형태로 제공한다. iSCSI나 NFS와 같은 온프렘 기반의 일반적인 외장 스토리지 이외에도, 클라우드의 외장 스토리지인 AWS EBS, Google PD,에서 부터  github, glusterfs와 같은 다양한 오픈소스 기반의 외장 스토리지나 스토리지 서비스를 지원하여, 스토리지 아키텍처 설계에 다양한 옵션을 제공한다.

Service

Pod와 볼륨을 이용하여, 컨테이너들을 정의한 후에, Pod 를 서비스로 제공할때, 일반적인 분산환경에서는 하나의 Pod로 서비스 하는 경우는 드물고, 여러개의 Pod를 서비스하면서, 이를 로드밸런서를 이용해서 하나의 IP와 포트로 묶어서 서비스를 제공한다.


Pod의 경우에는 동적으로 생성이 되고, 장애가 생기면 자동으로 리스타트 되면서 그 IP가 바뀌기 때문에, 로드밸런서에서 Pod의 목록을 지정할 때는 IP주소를 이용하는 것은 어렵다. 또한 오토 스케일링으로 인하여 Pod 가 동적으로 추가 또는 삭제되기 때문에, 이렇게 추가/삭제된 Pod 목록을 로드밸런서가 유연하게 선택해 줘야 한다.

그래서 사용하는 것이 라벨(label)과 라벨 셀렉터(label selector) 라는 개념이다.


서비스를 정의할때, 어떤 Pod를 서비스로 묶을 것인지를 정의하는데, 이를 라벨 셀렉터라고 한다. 각 Pod를 생성할때 메타데이타 정보 부분에 라벨을 정의할 수 있다. 서비스는 라벨 셀렉터에서 특정 라벨을 가지고 있는 Pod만 선택하여 서비스에 묶게 된다.

아래 그림은 서비스가 라벨이 “myapp”인 서비스만 골라내서 서비스에 넣고, 그 Pod간에만 로드밸런싱을 통하여 외부로 서비스를 제공하는 형태이다.



이를 스펙으로 정의해보면 대략 다음과 같다.


kind: Service
apiVersion: v1
metadata:
 name: my-service
spec:
 selector:
   app: myapp
 ports:
 - protocol: TCP
   port: 80
   targetPort: 9376


  • 리소스 종류가 Service 이기 때문에, kind는 Service로 지정하고,

  • 스크립트를 실행할 api 버전은 v1으로 apiVersion에 정의했다.

  • 메타데이타에 서비스의 이름을 my-service로 지정하고

  • spec 부분에 서비스에 대한 스펙을 정의한다.

    • selector에서 라벨이 app:myapp인 Pod 만을 선택해서 서비스에서 서비스를 제공하게 하고

    • 포트는 TCP를 이용하되, 서비스는 80 포트로 서비스를 하되, 서비스의 80 포트의 요청을 컨테이너의 9376 포트로 연결해서 서비스를 제공한다.


Name space

네임스페이스는 한 쿠버네티스 클러스터내의 논리적인 분리단위라고 보면 된다.

Pod,Service 등은 네임 스페이스 별로 생성이나 관리가 될 수 있고, 사용자의 권한 역시 이 네임 스페이스 별로 나눠서 부여할 수 있다.

즉 하나의 클러스터 내에, 개발/운영/테스트 환경이 있을때, 클러스터를 개발/운영/테스트 3개의 네임 스페이스로 나눠서 운영할 수 있다. 네임스페이스로 할 수 있는 것은

  • 사용자별로 네임스페이스별 접근 권한을 다르게 운영할 수 있다.

  • 네임스페이스별로 리소스의 쿼타 (할당량)을 지정할 수 있다. 개발계에는 CPU 100, 운영계에는 CPU 400과 GPU 100개 식으로, 사용 가능한 리소스의 수를 지정할 수 있다.

  • 네임 스페이스별로 리소스를 나눠서 관리할 수 있다. (Pod, Service 등)


주의할점은 네임 스페이스는 논리적인 분리 단위이지 물리적이나 기타 장치를 통해서 환경을 분리(Isolation)한것이 아니다. 다른 네임 스페이스간의 pod 라도 통신은 가능하다.

물론 네트워크 정책을 이용하여, 네임 스페이스간의 통신을 막을 수 있지만 높은 수준의 분리 정책을 원하는 경우에는 쿠버네티스 클러스터 자체를 분리하는 것을 권장한다.


참고 자료 네임 스페이스에 대한 베스트 프랙틱스 : https://cloudplatform.googleblog.com/2018/04/Kubernetes-best-practices-Organizing-with-Namespaces.html

https://kubernetes.io/blog/2016/08/kubernetes-namespaces-use-cases-insights/

라벨

앞에서 잠깐 언급했던 것 중의 하나가 label 인데, 라벨은 쿠버네티스의 리소스를 선택하는데 사용이 된다. 각 리소스는 라벨을 가질 수 있고, 라벨 검색 조건에 따라서 특정 라벨을 가지고 있는 리소스만을 선택할 수 있다.

이렇게 라벨을 선택하여 특정 리소스만 배포하거나 업데이트할 수 있고 또는 라벨로 선택된 리소스만 Service에 연결하거나 특정 라벨로 선택된 리소스에만 네트워크 접근 권한을 부여하는 등의 행위를 할 수 있다.

라벨은 metadata 섹션에 키/값 쌍으로 정의가 가능하며, 하나의 리소스에는 하나의 라벨이 아니라 여러 라벨을 동시에 적용할 수 있다.


"metadata": {
 "labels": {
   "key1" : "value1",
   "key2" : "value2"
 }
}


셀렉터를 사용하는 방법은 오브젝트 스펙에서 selector 라고 정의하고 라벨 조건을 적어 놓으면 된다.

쿠버네티스에서는 두 가지 셀렉터를 제공하는데, 기본적으로 Equaility based selector와, Set based selector 가 있다.

Equality based selector는 같냐, 다르냐와 같은 조건을 이용하여, 리소스를 선택하는 방법으로

  • environment = dev

  • tier != frontend

식으로, 등가 조건에 따라서 리소스를 선택한다.

이보다 향상된 셀렉터는 set based selector로, 집합의 개념을 사용한다.

  • environment in (production,qa) 는 environment가 production 또는 qa 인 경우이고,

  • tier notin (frontend,backend)는 environment가 frontend도 아니고 backend도 아닌 리소스를 선택하는 방법이다.

다음 예제는 my-service 라는 이름의 서비스를 정의한것으로 셀렉터에서 app: myapp 정의해서 Pod의 라벨 app이 myapp 것만 골라서 이 서비스에 바인딩해서 9376 포트로 서비스 하는 예제이다.


kind: Service
apiVersion: v1
metadata:
 name: my-service
spec:
 selector:
   app: myapp
 ports:
 - protocol: TCP
   port: 80
   targetPort: 9376



컨트롤러

앞에서 소개한 4개의 기본 오브젝트로, 애플리케이션을 설정하고 배포하는 것이 가능한데 이를 조금 더 편리하게 관리하기 위해서 쿠버네티스는 컨트롤러라는 개념을 사용한다.

컨트롤러는 기본 오브젝트들을 생성하고 이를 관리하는 역할을 해준다. 컨트롤러는 Replication Controller (aka RC), Replication Set, DaemonSet, Job, StatefulSet, Deployment 들이 있다. 각자의 개념에 대해서 살펴보도록 하자.

Replication Controller

Replication Controller는  Pod를 관리해주는 역할을 하는데, 지정된 숫자로 Pod를 기동 시키고, 관리하는 역할을 한다.

Replication Controller (이하 RC)는 크게 3가지 파트로 구성되는데, Replica의 수, Pod Selector, Pod Template 3가지로 구성된다.

  • Selector : 먼저 Pod selector는 라벨을 기반으로 하여,  RC가 관리한 Pod를 가지고 오는데 사용한다.

  • Replica 수 :  RC에 의해서 관리되는 Pod의 수인데, 그 숫자만큼 Pod 의 수를 유지하도록 한다.예를 들어 replica 수가 3이면, 3개의 Pod만 띄우도록 하고, 이보다 Pod가 모자르면 새로운 Pod를 띄우고, 이보다 숫자가 많으면 남는 Pod를 삭제한다.

  • Pod를 추가로 기동할 때 그러면 어떻게 Pod를 만들지 Pod에 대한 정보 (도커 이미지, 포트,라벨등)에 대한 정보가 필요한데, 이는 Pod template이라는 부분에 정의 한다.




주의할점은 이미 돌고 있는 Pod가 있는 상태에서 RC 리소스를 생성하면 그 Pod의 라벨이 RC의 라벨과 일치하면 새롭게 생성된 RC의 컨트롤을 받는다. 만약 해당 Pod들이 RC에서 정의한 replica 수 보다 많으면, replica 수에 맞게 추가분의 pod를 삭제하고, 모자르면 template에 정의된 Pod 정보에 따라서 새로운 Pod를 생성하는데, 기존에 생성되어 있는 Pod가 template에 정의된 스펙과 다를지라도 그 Pod를 삭제하지 않는다. 예를 들어 기존에 아파치 웹서버로 기동중인 Pod가 있고, RC의 template은 nginx로 Pod를 실행하게 되어 있다하더라도 기존에 돌고 있는 아파치 웹서버 기반의 Pod를 삭제하지 않는다.


아래 예를 보자.


이 예제는 ngnix라는 이름의 RC를 정의한 것으로, label이 “app:ngnix”인 Pod들을 관리하고 3개의 Pod가 항상 운영되도록 설정한다.

Pod는 app:ngix 라는 라벨을 가지면서 이름이 ngnix이고 nginx 이미지를 사용해서 생성하고 컨테이너의 포트는 80 번 포트를 이용해서 서비스를 제공한다.

ReplicaSet

ReplicaSet은 Replication Controller 의 새버전으로 생각하면 된다.

큰 차이는 없고 Replication Controller 는 Equality 기반 Selector를 이용하는데 반해, Replica Set은 Set 기반의 Selector를 이용한다.

Deployment

Deployment (이하 디플로이먼트) Replication controller와 Replica Set의 좀더 상위 추상화 개념이다. 실제 운영에서는 ReplicaSet 이나 Replication Controller를 바로 사용하는 것보다, 좀 더 추상화된 Deployment를 사용하게 된다.

쿠버네티스 배포에 대한 이해

쿠버네티스의 Deployment 리소스를 이해하기 위해서는 쿠버네티스에서 Deployment 없이 어떻게 배포를 하는지에 대해서 이해를 하면 Deployment 를 이해할 수 있다.


다음과 같은 Pod와 RC가 있다고 하자


애플리케이션이 업데이트되서 새로운 버전으로 컨테이너를 굽고 이 컨테이너를 배포하는 시나리오에 대해서 알아보자. 여러가지 배포 전략이 있겠지만, 많이 사용하는 블루/그린 배포와 롤링 업데이트 방식 두가지 방법에 대해서 설명한다.

블루/그린 배포

블루/그린 배포 방식은 블루(예전)버전으로 서비스 하고 있던 시스템을 그린(새로운)버전을 배포한 후, 트래픽을 블루에서 그린으로 한번에 돌리는 방식이다.

여러가지 방법이 있지만 가장 손쉬운 방법으로는 새로운 RC을 만들어서 새로운 템플릿으로 Pod를 생성한 후에, Pod 생성이 끝나면, 서비스를 새로운 Pod로 옮기는 방식이다.


후에, 배포가 완료되고 문제가 없으면 예전 버전의 RC 와 Pod를 지워준다.

롤링 업그레이드

롤링 업그레이드 방식은 Pod를 하나씩 업그레이드 해가는 방식이다.

이렇게 배포를 하려면 먼저 새로운 RC를 만든후에, 기존 RC에서 replica 수를 하나 줄이고, 새로운 RC에는 replica 수를 하나만 준다.


라벨을 같은 이름으로 해주면 서비스는 자연히 새로운 RC에 의해 생성된 Pod를 서비스에 포함 시킨다.

다음으로 기존 RC의 replica를 하나 더 줄이고, 새로운 RC의  replica를 하나 더 늘린다.


그러면 기존 버전의 Pod가 하나더 서비스에서 빠지게 되고 새로운 버전의 Pod가 서비스에 추가된다.

마찬가지 작업을 반복하게 되면, 아래 그림과 같이 예전 버전의 Pod가 모두 빠지고 새 버전의 Pod만 서비스 되게 된다.


만약에 배포가 잘못되었을 경우에는 기존 RC의 replica 수를 원래대로 올리고, 새버전의 replicat 수를 0으로 만들어서 예전 버전의 Pod로 롤백이 가능하다.

이 과정은 kubectl rolling-update라는 명령으로 RC 단위로 컨트롤이 가능하지만, 그래도 여전히 작업이 필요하고, 배포 과정을 모니터링 해야 한다. 그리고 가장 문제는 kubectl rolling-update 명령은 클라이언트에서 실행 하는 명령으로, 명령어 실행중에 클라이언트의 연결이 끊어 지면 배포작업이 비정상적으로 끊어질 수 있는 문제가 있다.

그리고 마지막으로, 롤백과정 역시 수동 컨트롤이 필요할 수 있다.

그래서 이러한 과정을 자동화하고 추상화한 개념을 Deployment라고 보면 된다.

Deployment는 Pod 배포를 위해서 RC를 생성하고 관리하는 역할을 하며, 특히 롤백을 위한 기존 버전의 RC 관리등 여러가지 기능을 포괄적으로 포함하고 있다.



Deployment 에 대해서는 뒤에 다른 글에서 조금 더 자세하게 설명하도록 한다.


이글에서는 쿠버네티스를 이루는 기본적인 오브젝트와 이를 생성 제어하기 위한 기본적인 컨트롤러에 대해서 알아보았다.

다음 글에서는 조금 더 발전된 형태의 컨트롤러에 대해서 알아보기로 한다.







Kubernetes #1 - 소개

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

배경

도커와 쿠버네티스를 알게 된건 수년전인데, 근래에 들어서 다시 쿠버네티스를 보기 시작하였다.

컨테이너 기반의 환경은 배포에 장점이 있고 마이크로 서비스 아키텍쳐 구조에 잘 맞아들어가는 듯 싶지만, 컨테이너가 약간 빠르다는 장점은 있지만, 가상 머신으로도 충분히 패키징이 가능하고, 로컬의 개발환경을 동기화 시키는 장점은 vagrant 로도 충분하다는 생각을 가지고 있었다.


그리고 결정적으로 도커 컨테이너를 운용하기 위한 컨테이너 관리 환경이 그다지 성숙하지 못했었다. Mesosphere, Swarm, Kubernetes 등 다양한 환경이 나오기는 하였지만 기능적으로 부족한 부분도 많았고, 딱히 어떤 플랫폼이 대세라고 정해진것도 없었다.


마이크로 서비스 아키텍쳐 발전

그러나 근래에 들어서 재미있어지는 현상이 마이크로 서비스 아키텍쳐가 단순 개념에서 부터 점점 더 발전하기 시작하였고, 디자인 패턴과 이를 구현하기 위한 다양한 인프라 플랫폼들이 소개되기 시작하였다.

또한 서비스가 점점 작아지면서, 1~2 코어로도 운영할 수 있는 작은 서비스들이 다수 등장하게 되었고 이런 작은 서비스는 VM 환경으로 운영하기에는 낭비가 너무 심하다. (VM 이미지 크기도 너무 크고, 다양한 이미지를 VM으로 관리 배포하기에는 배포 속도등 다양한 문제가 발생한다.)


솔루션의 발전

배포 방식도 예전에 서버에 계속해서 애플리케이션 코드만 업데이트 하는 방식이 아니라, VM이나 컨테이너 단위로 배포하는 피닉스 서버 패턴과 이를 구현하기 위한 Spinnaker  와 같은 솔루션이 나오고 있고, 지능형 라우팅과 분산 트렌젝션 로그 추적을 하는 기능들이 Envoy 라는 솔루션으로 나오고 이를 중앙 통제하기 위한 Istio.io 와 같은 서비스 메쉬 솔루션 까지 나오기에 이르렀다.


데브옵스 모델의 성숙화

데브옵스 모델도 나온지는 오래되었지만, 운영을 데브옵스라는 이름으로 바꾼 것일뿐 실제적인 변화가 없는 팀들이 많았고, 또는 데브옵스라는 이름아래에서 개발팀이 개발과/운영 역할을 병행해서 하는 사례가 오히려 많았다.

이런 데브옵스의 개념도 근래에 들어서 정리가 되어가고 있는데, 개발팀이 개발과 시스템에 대한 배포/운영을 담당한다면, 데브옵스팀은 개발팀이 이를 쉽게할 수 있는 아랫단의 플랫폼과 자동화를 하는데 목표를 두는 역할로 역할이 명확해지고 있다.


이러한 배경에서 슬슬 컨테이너 기반의 환경이 실질적으로 적용될만하다는 것으로 판단하였고, 다시 컨테이너 환경에 대해서 살펴보기 시작하였다.

왜 하필이면 쿠버네티스인가?

그렇다면 Swarm,Mesosphere 가 아니라 왜 하필이면 쿠버네티스인가? 컨테이너 운용 환경은 여러 오픈소스에 의해서 표준이 없이 혼돈이었다가 작년말을 기점으로 해서 쿠버네티스가 de-facto 표준으로 되어가는 형국이다. 아래 트랜드 그래프에서 보면 알 수 있듯이 쿠버네티스의 트랜드가 지속적으로 올라가서 가장 높은 것을 확인할 수 있다.



또한 주요 클라우드 벤더인 아마존,구글,애저 모두 컨테이너 관리 환경을 쿠버네티스를 지원하는 정책으로 변화된것은 물론이고 IBM이나 시스코와 같은 온프렘(on-premise) 솔루션 업체들도 경쟁적으로 쿠버네티스를 지원하고 있다.

컨테이너 운영환경이 무엇인데?

컨테이너 (도커)에 필요성과 마이크로 서비스의 관계등에 대해서는 워낙 소개된 글들이 많아서 생략한다. 그렇다면 쿠버네티스가 제공하는 컨테이너 운영환경이란 무엇인가? 이를 이해하기 위해서는 먼저 컨테이너에 대해서 이해할 필요가 있는데, 컨테이너의 가장 대표적인 예로는 도커가 있다. 도커에 대한 자세한 설명은 링크를 참고하기 바란다.


그러면 단순하게 도커 컨테이너를 하드웨어나 VM에 배포하면 사용하면 되지 왜 컨테이너 운영환경이 필요한가?


작은 수의 컨테이너라면 수동으로 VM이나 하드웨어에 직접 배포하면 되지만, VM이나 하드웨어의 수가 많아지고 컨테이너의 수가 많아지면, 이 컨테이너를 어디에 배포해야 하는지에 대한 결정이 필요하다.

16 CPU, 32 GB 메모리 머신들에 컨테이너를 배포할때 컨테이너 사이즈가 2 CPU, 3 CPU, 8 CPU등 다양할 수 있기 때문에, 자원을 최대한 최적으로 사용하기 위해서 적절한 위치에 배포해야 하고, 애플리케이션 특성들에 따라서, 같은 물리 서버에 배포가 되어야 하거나 또는 가용성을 위해서 일부러 다른 물리서버에 배포되어야 하는 일이 있다. 이렇게 컨테이너를 적절한 서버에 배포해주는 역할을 스케쥴링이라고 한다.


이러한 스케쥴링 뿐만이 아니라 컨테이너가 정상적으로 작동하고 있는지 체크하고 문제가 있으면 재 기동등을 해주고, 모니터링, 삭제관리등 컨테이너에 대한 종합적인 관리를 해주는 환경이 필요한데, 이를 컨테이너 운영환경이라고 한다.

쿠버네티스란?

이런 컨테이너 운영환경중 가장 널리 사용되는 솔루션이 쿠버네티스 (Kubernetes, 약어로 k8s)라고 한다.

구글은 내부 서비스를 클라우드 환경에서 운영하고 있으며, 일찌감치 컨테이너 환경을 사용해왔다. 구글의 내부 컨테이너 서비스를 Borg라고 하는데, 이 구조를 오픈소스화한것이 쿠버네티스이다.

GO 언어로 구현이되었으며, 특히 재미있는 것은 벤더나 플랫폼에 종속되지 않기 때문에, 대부분의 퍼블릭 클라우드 (구글,아마존,애저)등에 사용이 가능하고 오픈 스택과 같은 프라이빗 클라우드 구축 환경이나 또는 베어메탈 (가상화 환경을 사용하지 않는 일반 서버 하드웨어)에도 배포가 가능하다.

이런 이유 때문에 여러 퍼블릭 클라우드를 섞어서 사용하는 환경이나 온프렘/퍼블릭 클라우드를 혼용해서 쓰는 환경에도 동일하게 적용이 가능하기 때문에 하이브리드 클라우드 솔루션으로 많이 각광 받고 있다.


흔히들 컨테이너를 이야기 하면 도커를 떠올리기 쉬운데, 도커가 물론 컨테이너 엔진의 대표격이기는 하지만 이 이외도 rkt나 Hyper container(https://hypercontainer.io/) 등 다양한 컨테이너 엔진들이 있으며, 쿠버네티스는 이런 다양한 컨테이너 엔진을 지원한다.

컨테이너 환경을 왜 VM에 올리는가?

온프렘 환경(데이타센터)에서 쿠버네티스를 올릴때 궁금했던점 중의 하나가, 바로 베어메탈 머신위에 쿠버네티스를 깔면 되는데, 보통 배포 구조는 VM(가상화 환경)을 올린 후에, 그 위에 쿠버네티스를 배포하는 구조를 갖는다. 왜 이렇게 할까 한동안 고민을 한적이 있었는데, 나름데로 내린 결론은 하드웨어 자원 활용의 효율성이다. 컨테이너 환경은 말그대로 하드웨어 자원을 컨테이너화하여 isolation 하는 기능이 주다. 그에 반해 가상화 환경은 isolation 기능도 가지고 있지만, 가상화를 통해서 자원 , 특히 CPU의 수를 늘릴 수 있다.


예를 들어 설명하면, 8 CPU 머신을 쿠버네티스로 관리 운영하면, 8 CPU로밖에 사용할 수 없지만, 가상화 환경을 중간에 끼면, 8 CPU를 가상화 해서 2배일 경우 16 CPU로, 8배일 경우 64 CPU로 가상화 하여 좀 더 자원을 잘게 나눠서 사용이 가능하기 때문이 아닌가 하는 결론을 내렸다.

이 이외에도 스토리지 자원의 활용 용이성이나 노드 확장등을 유연하게 할 수 있는 장점이 있다고 한다.


다음 글에서는 쿠버네티스를 구성하는 컴포넌트들의 구성과 개념에 대해서 설명하도록 한다.




Spinnaker #2 - 설치


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


설치


설치 문서는 https://www.spinnaker.io/setup/  를 참고하면 된다.

설치 가이드를 보면 Quick Install 가이드와 수동 인스톨 가이드를 제공하고 있다.



퀵 인스톨 가이드는 대규모 운영용으로는 어렵고 하나의 인스턴스에, 모든 마이크로 서비스가 인스톨 되는 모델로, 소규모 운영이나 또는 데모용으로 손쉽게 사용이 가능하다. 수동으로 인스톨 하는 방법은 다소 까다롭기 때문에, Quick Install 부터 진행하는 것을 권장한다.


Quick Install 페이지에 들어가면, 각 클라우드와 쿠버네티스 클러스터에 설치 하는 방법이 가이드 되어 있다.


구글 클라우드에 Spinnaker 설치

구글 클라우드에 Spinnaker를 설치하려면,간단하게  Google Cloud Launcher를 이용하면 손쉽게 설치가 가능하다.

설치에 앞서서, Spinnaker는 Google Cloud API를 이용하여, 인스턴스 생성과 스토리지등을 접근하기 때문에, 구글 클라우드 프로젝트에서 해당 API들을 활성화 해줘야 한다. (매우 중요) 활성화 해야하는 API는 아래와 같다.


다음은 Spinnaker를 설치해보자.

Cloud Launcher 페이지에서 Spinnaker를 선택한 후



“Launch on compute engine” 을 선택하면, 4 CPU/20GB VM에 Spinnaker가 설치된다.

아래와 같이 인스턴스 이름을 입력하고, Zone을 선택하면 된다.만약에 필요하다면 VM의 Machine Type을 좀 큰 인스턴스로 해서 운영환경에 적용해도 된다.




만약에 Kubernetes Cluster를 연동해서 사용하고자 한다면, https://www.spinnaker.io/guides/tutorials/codelabs/gcp-kubernetes-source-to-prod/#enable-apis 설치 가이드를 참고하기 바란다.

실행

Spinnaker 를 디폴트로 설치하고 나면 별도의 ID 인증 기능이 설정되어 있지 않기 때문에, 접속 포트가 인터넷으로 열려 있지 않고 local host 로만 접속을 허용한다.

그래서 SSH 터널링을 통해서 로컬 PC의 Local port 9000번과 8084번 포트를 Spinnaker VM의 포트와 맵핑을 시키도록 한다.

9000번은 웹사이트 8084번은 웹사이트가 호출하는 gate 컴포넌트이다.

SSH 터널링은 gcloud compute ssh명령을 이용하면 된다.

사용 방법은 다음과 같다.

gcloud compute ssh --project={구글 클라우드 프로젝트명} --zone={Spinnaker 인스턴스가 배포된 존 이름} {인스턴스명} -- -L 9000:localhost:9000  -L 8084:localhost:8084


다음은 terrycho-sandbox라는 프로젝트 명을 가지고 spinnaker-demo 라는 인스턴스를 asia-northeast-1c에 배포해놓고 접속한 예이다.


gcloud compute ssh --project=terrycho-sandbox --zone=asia-northeast1-c spinnaker-demo -- -L 9000:localhost:9000  -L 8084:localhost:8084


명령을 실행한 후에, 브라우져에서 localhost:9000 번으로 접속하면 Spinnaker 콘솔을 볼 수 있다.



Apache Beam (Dataflow)를 이용하여, 이미지 파일을 tfrecord로 컨버팅 하기


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



개요

텐서플로우 학습에 있어서 데이타 포맷은 학습의 성능을 결정 짓는 중요한 요인중의 하나이다. 특히 이미지 파일의 경우 이미지 목록과 이미지 파일이 분리되어 있어서 텐서플로우에서 학습시 이미지 목록을 읽으면서, 거기에 있는 이미지 파일을 매번 읽어야 하기 때문에, 코딩이 다소 지저분해지고,IO 성능이 떨어질 수 있다

텐서플로우에서는 이러한 학습 데이타를 쉽게 읽을 수 있도록 tfrecord (http://bcho.tistory.com/1190)라는 파일 포맷을 지원한다.


이 글에서는 이미지 데이타를 읽어서 tfrecord 로 컨버팅하는 방법을 설명하며, 분산 데이타 처리 프레임웍인 오픈소스 Apache Beam을 기준으로 설명하나, tfrecord 변환 부분은 Apache Beam과 의존성이 없이 사용이 가능하기 때문에, 필요한 부분만 참고해도 된다. 이 Apache Beam을 구글의 Apache Beam 런타임 (매니지드 서비스)인 구글 클라우드의 Dataflow를 이용하여, 클러스터를 이용하여 빠르게 데이타를 처리하는 방법에 대해서 알아보도록 한다.


전체 코드는 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/4.%20Convert%20Pickle%20file%20to%20TFRecord%20by%20using%20Apache%20Beam.ipynb 에 있다.


이 코드는 CIFAR-10 이미지 데이타를 Apache Beam 오픈 소스를 이용하여, 텐서플로우 학습용 데이타 포맷인  tfrecord 형태로 변환 해주는 코드이다.


Apache Beam은 데이타 처리를 위한 프레임웍으로, 구글 클라우드 상에서 실행하거나 또는 개인 PC나 Spark 클러스터상 여러 환경에서 실행이 가능하며, 구글 클라우드 상에서 실행할 경우 오토스케일링이나 그래프 최적화 기능등으로 최적화된 성능을 낼 수 있다.


CIFAR-10 데이타 셋은 32x32 PNG 이미지 60,000개로 구성된 데이타 셋으로 해당 코드 실행시 최적화가 되지 않은 상태에서 약 16분 정도의 처리 시간이 소요된다. 이 중 6분 정도는 Apache Beam 코드를 구글 클라우드로 업로드 하는데 소요되는 시간이고 실제 처리시간은 10분정도가 소요된다. 전처리 과정에 Apache Beam을 사용하기 전에 고려해야 할 요소는 다음과 같다.

  • 데이타가 아주 많아서 전처리 시간이 수시간 이상 소요될 경우 Apache Beam + Google Cloud를 고려하여 여러 머신에서 동시에 처리하여 빠른 시간내에 수행되도록 할 수 있다.

  • 데이타가 그다지 많지 않고 싱글 머신에서 멀티 쓰레드로 처리를 원할 경우에는 Apache Beam으로 멀티 쓰레드 기반의 병렬 처리를 하는 방안을 고려할 수 있다. 이 경우 클라우드에 대한 의존성을 줄일 수 있다.

  • 다른 대안으로는 Spark/Hadoop 등의 오픈소스를 사용하여, On Prem에서 여러 머신을 이용하여 전처리 하는 방안을 고려할 수 있다.

여기서는 아주 많은 대량의 이미지 데이타에 대한 처리를 하는 것을 시나리오로 가정하였다.

전처리 파이프라인

Apache Beam을 이용한 데이타 전처리 파이프라인의 구조는 다음과 같다.

이미지 파일 준비

CIFAR-10 데이타셋 원본은 이미지 파일 형태가 아니라 PICKLE이라는 파일 포맷으로 되어 있기 때문에,  실제 개발 환경에서는 원본데이타가 이미지인것으로 가정하기 위해서 https://github.com/bwcho75/cifar-10/tree/master/pre-processing 의 1~2번 코드를 통해서 Pickle 파일을 이미지 파일로 변경하고, *.csv 파일에 {파일명},{레이블} 형태로 인덱스 데이타를 생성하였다.

생성된 이미지 파일과 *.csv 파일은 gsutil 명령어를 이용하여 Google Cloud Storage (aka GCS)에 업로드 하였다. 업로드 명령은 https://github.com/bwcho75/cifar-10/blob/master/pre-processing/2.%20Convert%20CIFAR-10%20Pickle%20files%20to%20image%20file.ipynb 에 설명되어 있다.


전처리 파이프라인의 구조

Apache Beam으로 구현된 파이프라인의 구조는 다음과 같다.


1. TextIO의 ReadFromText로 CSV 파일에서 한 라인 단위로 문자열을 읽는다.

2. parseLine에서 라인을 ,로 구분하여 filename과 label을 추출한다.

3. readImage 에서 filename을 가지고, 이미지 파일을 읽어서, binary array 형태로 변환한다.

4. TFExampleFromImageDoFn에서 이미지 바이너리와 label을 가지고 TFRecord 데이타형인 TFExample 형태로 변환한다.

5. 마지막으로 TFRecordIOWriter를 통해서 TFExample을 *.tfrecord 파일에 쓴다.

코드 주요 부분 설명

환경 설정 부분

이 코드는 구글 클라우드와 로컬 환경 양쪽에서 모두 실행이 가능하도록 구현되었다.

SRC_DIR_DEV는 로컬환경에서 이미지와 CSV 파일이 위치한 위치이고, DES_DIR_DEV는 로컬환경에서 tfrecord 파일이 써지는 위치이다.

구글 클라우드에서 실행할 경우 파일 저장소를  GCS (Google Cloud Storage)를 사용한다. DES_BUCKET은 GCS 버킷 이름이다. 코드 실행전에 반드시 구글 클라우드 콘솔에서 GCS 버킷을 생성하기 바란다.  SRC_DIR_PRD와 DES_DIR_PRD는 GCS 버킷내의 각각 image,csv 파일의 경로와 tfrecord 파일이 써질 경로 이다. 이 경로에 맞춰서 구글 클라우드 콘솔에서 디렉토리를 먼저 생성해 놓기를 바란다.




PROJECT는 구글 클라우드 프로젝트 명이고, 마지막으로 DEV_MODE가 True이면 로컬에서 수행이되고 False이면 구글 클라우드에서 실행하도록 하는 환경 변수이다.

의존성 설정 부분

로컬에서 실행할 경우필요한  파이썬 라이브러리가 이미 설치되어야 있어야 한다.

만약에 구글 클라우드에서 실행할 경우 이 Apache Beam 코드가 사용하는 파이썬 모듈을 명시적으로 정의해놔야 한다. 클라우드에서 실행시에는 Apache Beam 코드만 업로드가 되기 때문에(의존성 라이브러리를 같이 업로드 하는 방법도 있는데, 이는 추후에 설명한다.), 의존성 라이브는 구글 클라우드에서 Dataflow 실행시 자동으로 설치할 수 있도록 할 수 있는데, 이를 위해서는 requirements.txt 파일에 사용하는 파이썬 모듈들을 정의해줘야 한다. 다음은 requirements.txt에 의존성이 있는 파이썬 모듈등을 정의하고 저장하는 부분이다.


Apache Beam 코드

Apache Beam의 코드 부분은 크게 복잡하지 않기 때문에 주요 부분만 설명하도록 한다.

Service account 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 코드 실행에 대한 권한을 줘야 한다. 구글 클라우드에서는 사용자가 아니라 애플리케이션에 권한을 부여하는 방법이 있는데, Service account라는 것을 사용한다. Service account는 json 파일로 실행 가능한 권한을 정의하고 있다.

Service account 파일을 생성하는 방법은 http://bcho.tistory.com/1166 를 참고하기 바란다.

Service account 파일이 생성되었으면, 이 파일을 적용해야 하는데 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 Service account  파일의 경로를 정의해주면 된다. 파이썬 환경에서 환경 변수를 설정하는 방법은 os.envorin[‘환경변수명']에 환경 변수 값을 지정해주면 된다.

Jobname 설정

구글 클라우드에서 Apache Beam 코드를 실행하면, 하나의 실행이 하나의 Job으로 생성되는데, 이 Job을 구별하기 위해서 Job 마다 ID 를 설정할 수 있다. 아래는 Job ID를 ‘cifar-10’+시간 형태로 지정하는 부분이다


환경 설정

Apache Beam 코드를 구글 클라우드에서 실행하기 위해서는 몇가지 환경을 지정해줘야 한다.


  • staging_location은 클라우드 상에서 실행시 Apache Beam 코드등이 저장되는 위치이다. GCS 버킷 아래 /staging이라는 디렉토리로 지정했는데, 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • temp_location은 기타 실행중 필요한 파일이 저장되는 위치이다. 실행 전에 반드시 버킷아래 디렉토리를 생성하기 바란다.

  • zone은 dataflow worker가 실행되는 존으로 여기서는 asia-northeast1-c  (일본 리전의 c 존)으로 지정하였다.


DEV_MODE 에 따른 환경 설정

로컬 환경이나 클라우드 환경에서 실행이냐에 따라서 환경 변수 설정이 다소 달라져야 한다.


디렉토리 경로를 바꿔서 지정해야 하고, 중요한것은 RUNNER인데, 로컬에서 실행하기 위해서는 DirectRunner를 구글 클라우드 DataFlow 서비스를 사용하기 위해서는 DataflowRunner를 사용하면 된다.


readImage 부분

Read Image는 이미지 파일을 읽어서 byte[] 로 리턴하는 부분인데, 로컬 환경이냐, 클라우드 환경이냐에 따라서 동작 방식이 다소 다르다.

클라우드 환경에서는 이미지 파일이 GCS에 저장되어 있기 때문에 파이썬의 일반 파일 open 명령등을 사용할 수 없다.

그래서 클라우드 환경에서 동작할 경우에는 GCS에서 파일을 읽어서 Worker의 로컬 디스크에 복사를 해놓고 이미지를 읽어서 byte[]로 변환한 후에, 해당 파일을 지우는 방식을 사용한다.


아래 코드에서 보면 DEV_MODE가 False 인경우 GCS에서 파일을 읽어서 로컬에 저장하는 코드가 있다.


storageClient는 GCS 클라이언트이고 bucket 을 얻어온후, bucket에서 파일을 get_blob 명령어를 이용하여 경로를 저장하여 blob.download_to_file을 이용하여 로컬 파일에 저장하였다.

실행

코드 작성이 끝났으면 실행을 한다. 실행 상태는 구글 클라우드 콘솔의 Dataflow  메뉴에서 확인이 가능하다.

아래와 같이 실행중인 그리고 실행이 끝난 Job 리스트들이 출력된다.




코드 실행중에, 파이프라인 실행 상황 디테일을 Job 을 선택하면 볼 수 있다.


여기서 주목할만한 점은 우측 그래프인데, 우측 그래프는 Worker의 수를 나타낸다. 초기에 1대로 시작했다가 오토 스케일링에 의해서 9대 까지 증가한것을 볼 수 있다.

처음 실행이었기 때문에 적정한 인스턴스수를 몰랐기 때문에 디폴트로 1로 시작하고 오토스케일링을 하도록 했지만, 어느정도 테스트를 한후에 적정 인스턴수를 알면 오토 스케일링을 기다릴 필요없이 디폴트 인스턴스 수를 알면 처음부터 그 수만큼 인스턴스 수로 시작하도록 하면 실행 시간을 줄일 수 있다.

만약에 파이프라인 실행시 에러가 나면 우측 상단에 LOGS 버튼을 누르면 상세 로그를 볼 수 있다.


아래 그림은 파이프라인 실행이 실패한 예에서 STACK TRACES를 통해서 에러 내용을 확인하는 화면이다.



해당 로그를 클릭하면 Stack Driver (구글의 모니터링 툴)의 Error Reporting 시스템 화면으로 이동하게 된다.

여기서 디테일한 로그를 볼 수 있다.

아래 화면을 보면 ReadImage 단계에서 file_path라는 변수명을 찾을 수 없어서 나는 에러를 확인할 수 있다.


TFRecord 파일 검증

파이프라인 실행이 끝나면, GCS 버킷에 tfrecord 파일이 생성된것을 확인할 수 있다.


해당 파일을 클릭하면 다운로드 받을 수 있다.

노트북 아래 코드 부분이 TFRecord를 읽어서 확인하는 부분이다. 노트북에서 tfrecord 파일의 경로를 다운로드 받은 경로로 변경하고 실행을 하면 파일이 제대로 읽히는 지 확인할 수 있다.


파일 경로 부분은 코드상에서 다음과 같다.



정상적으로 실행이 된 경우, 다음과 같이 tfrecord에서 읽은 이미지와 라벨값이 출력됨을 확인할 수 있다.


라벨 값은 Label 줄에 values 부분에 출력된다. 위의 그림에서는 순서대로 라벨 값이 4와 2가 된다.



NMF 알고리즘을 이용한 유사한 문서 검색과 구현(1/2)



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


앞의 글들에서, 데이타의 특징을 뽑아내는 방법으로 차원 감소 (Dimension reduction) 기법에 대해서 설명하였다. 구체적인 알고리즘으로는  PCA와 t-SNE 알고리즘을 소개하였는데, 오늘은 차원 감소 기법중의 하나인 행렬 인수분해 (Matrix Factorization)에 대해서 알아보고자 한다.

문서 유사도 검색

행렬 인수 분해를 설명하기 위해서 유사한 문서를 찾는 시나리오를 예를 들어서 설명하겠다.

문서 유사도 검색의 원리는 다음과 같다


  1. 문서에 나온 각 단어들을 숫자(벡터)로 변환하여 행렬화 한다.

  2. 행렬화된 문서에서 차원 감소 기법을 이용하여, 문서의 특징을 추출한다.

  3. 추출된 특징을 기반으로, 해당 문서와 특징이 유사한 문서를 찾아서 유사값을 기반으로 소팅하여, 유사 문서를 찾아낸다.


각 과정에서 사용할 알고리즘을 보면 다음과 같다.


  1. 문서의 단어들을 숫자화 하여, 행렬로 변환하는 과정에서는 여러가지 word2Vec (요즘 대세) 알고리즘이 있지만, 간단하게 tfidf 라는 알고리즘을 사용하겠다.

  2. 다음 문서의 행렬을 값을 가지고 특징을 추천하기 위해서는 앞에서 언급한 행렬 인수 분해 (Matrix Factorization) 알고리즘을 이용하여, 행렬의 차원을 줄일것이고

  3. 해당 문서와 특징 값이 유사한 문서를 찾기 위한 방법으로는 벡터간의 거리를 측정하는 방법을 사용하여 유사도를 측정하는데, Consine distance (코싸인 거리) 알고리즘을 사용하도록 한다.


각 알고리즘에 대한 간략한 개념을 설명하고 구현은 파이썬의 sklearn의 라이브러리를 사용해서 구현하도록 하겠다.그러면 각 알고리즘에 대한 설명을 보자

TF-IDF (Term Frequency Inverse Document Frequency)

TF-IDF를 이해하기 위해서는 먼저 TF(Term Frequency)와 DF(Document Frequency)에 대한 개념을 먼저 이해해야 한다

  • TF(Term Frequency) : TF랑, 하나의 문서에서 그 단어(Term) 가 얼마나 자주 나타나는가(Frequency)를 정의한 값이다.
    예를 들어, 한문서에서 “조대협" 이라는 단어가 10번 등장했다면 조대협에 대한 TF값은 10이 된다.

  • DF(Document Frequency) : DF란, 전체 문서(Document)에서 그 단어(Term)가 등장한 문서의 수를 나타낸다.  “조대협" 이라는 단어가 20개의 문서에 나타났다고 하면 DF 값은 20이 된다.

그러면 TF-IDF란 무엇인가 TF값을 DF값으로 나눈 값을 TFIDF라고 하는데, 위의 설명에서 “조대협" 이라는 단어에 대한 TFIDF값은 10/20=0.5 가 된다. (정확하게는 IDF는 log 를 포함한 다른 수식을 사용하지만 의미상으로 DF를 나눈것과 같이 문서에 등장하는 단어의 밀도를 나타낸다고 이해하면 된다.)


TF-IDF값의 의미는 무엇일까?

10년치 뉴스 문서가 있다고 가정하자.

그리고 우리가 뉴스에  많이 사용하는 단어로 “예를 들어" 라는 단어가 있다고 가정하자. (원래는 단어별로 잘라서 생각해야 하지만 설명을 쉽게 하기 위해서 두 단어를 하나의 단어로 생각하자). “예를 들어" 라는 단어는 어떤 문서의 특징을 나타내기는 사실 어렵다. 너무 일반적으로 사용되는 말이기 때문인데, 이런 단어의 경우에는 거의 모든 문서에 나타날 수 있기 때문에 DF 값이 매우 커진다. 그래서 “예를 들어"의 TF-IDF 값은 거의 0으로 수렴하게 되고, “세월호"와 같은 단어는 세월호를 언급한 뉴스 기사에만 있기 때문에, DF 값은 낮아질것이고, 결과적으로 TF-IDF 값은 커질 수 있는데, 세월호 라는 단어가 많이 언급된 문서일 수 록 TF-IDF 값이 커지게 된다.


눈치가 빠른 사람은 벌써 이해했겠지만, TFIDF의 기본 원리는 전체 문서에 널리 사용되는 일반적인 단어는 특징에서 배제하고, 문서에 특정 단어가 많이 언급될 수 록 그 단어의 TF-IDF값을 크게 하여, 문서의 특징을 나타내는데 사용할 수 있다.

NMF (Non-negative Matrix Factorization)

NMF (비음수 행렬 인수 분해)는 차원 감소 기법으로, 컴퓨터 시각 처리나, 우리가 하려는 문서 분류 그리고 음파 분석등에 널리 사용된다. 앞의 글들에서 소개했던 PCA나 t-SNE와 같은 차원 감소 기법에 대비한 차이를 보면, NMF에 의해 추출된 특징의 경우에는 해석이 가능하다는 장점을 가지고 있다.

PCA나 t-SNE는 원본 데이타의 특징을 추출하여 새로운 특징 셋으로 표현을 하지만 새로운 특징셋이 원본 특징과 어떤 연관 관계를 가지는지는 해석이 불가능하다. NMF의 경우 새로운 특징셋이 어떻게 원본 데이타 들과 관계를 가지는지 확인이 가능한데 이 부분은 NMF 알고리즘을 먼저 이해하고 하도록 하자


NMF는 행렬 인수 분해 알고리즘 중 하나로, 행렬 인수 분해란 다음과 같다.

우리가 원본 행렬 V가 있다고 했을때, 행렬 인수 분해는 이 V 행렬을 두개의 행렬로 분리 하는 것이다.

아래 그림은 원본 행렬 V 를 V=W*H 로 분리한 예이다.


예를 들어 TF-IDF를 이용하여 책 제목과 단어로 이루어진 행렬 V의 모양이 다음과 같다고 하자



책제목

협상

스타트업

투자

비지니스

데이타

...

...

협상의법칙

0.9

0

0.3

0.8

0



린스타트업

0

0.8

0.7

0.9

0.3



빅데이타

0

0

0.5

0

0.8



< 그림. 책 제목과, 그 책에 나온 단어의 TFIDF 값으로 이루어진 행렬 V >


이를 행렬 분해를 통하면


행렬 W는 다음과 같은 모양을 가지게 되고

책제목

특징1

특징2

특징3

특징4

협상의법칙

0.9

0

0.1

0.2

린스타트업

0

0.8

0

0

빅데이타

0.2

0.1

0.8

0.1


행렬 H는 다음과 같은 모양을 가지게 된다.


협상

스타트업

투자

비지니스

데이타

...

...

특징1

0.92

0

0.1

0.2

0



특징2

0

0.85

0.5

0.3

0.3



특징3

0

0

0.3

0

0.8



특징4

0

0

0

0

0

...



여기서 W를 가중치 행렬 (Weight Matrix), H를 특성 행렬 (Feature Matrix)라고 한다.

W는 카테고리 (책제목) 에 특성과의 관계를 나타내며, H는 원래 특성(협상,스타트업,...)에 대비한 새로운 특성(특징 1,2,3…)에 대한 관계를 나타낸다.


W 값을 보면 특징 1은 “협상의 법칙"에 자주 나오는 단어들 빈도와 관련이 높은 특성임을 알 수 있고, 특징 2는 “린스타트업", 특징3은 “빅데이타"에 자주 나오는 단어들의 빈도와 관련이 높은 특성임을 예측할 수 있다.

또한 특성 행렬 H를 보면 특징 1은 “협상" 이라는 단어와 관련이 많고, 특징 2는 “스타트업" 이라는 단어와 관련이 많은 것을 알 수 있다.

아래는 그림은  특징을 NMF를 이용하여 추출한 특성 행렬 H를 나타내는데, 해당 특징이 어떤 단어들에 의해서 반응 하는지를 알 수 있다.


그래서 NMF를 PCA나 t-SNE와 같은 다른 알고리즘과 비교했을때 특성을 해석 가능하다고 이야기 하는 것이다.


이 데이타셋에 만약에 이 데이타에 “스타트업 데이타 분석" 이라는 책이 들어왔고, 이 책은 빅데이타와 스타트업에 대해 다루고 있다면, 아마도 가중치 행렬 W에서 “스타트업 데이타 분석"은 다음과 같이 특징2와 3과 관련이 높은 형태를 나타낼 것이다.


책제목

특징1

특징2

특징3

특징4

협상의법칙

0.9

0

0.1

0.2

린스타트업

0

0.8

0

0

빅데이타

0.2

0.1

0.8

0.1

스타트업 데이타 분석

0

0.9

0.7

0


여기서 중요한 것은 차원을 줄일때 몇개의 특징으로 기존의 특성을 압축(줄일가)인데, 여기서는 4개의 특징으로 전체 특성을 줄이는 것으로 하였다. (특징을 몇개로 표현할 것인가가 매우 중요한 튜닝 패러미터가 된다.)


지금까지 설명한 행렬 인수 분해 방식은 행렬의 값이 양수일때 사용하는 행렬 분해 방식으로 “비음수 행렬 인수 분해 (Non negative Matrix Factorization)이라고 한다. NMF 방식에도 여러가지 다양한 발전된 알고리즘들이 있으며, 알고리즘 리스트는 https://en.wikipedia.org/wiki/Non-negative_matrix_factorization 를 참고하기 바란다.



코싸인 거리기반의 유사도 측정

NMF등을 이용하여 압축되어 있는 특성을 기반으로 하여 유사한 문서를 찾는 방법에 대해서 알아보자. 특성을 기반으로 유사도를 측정하는 방법은 여러가지가 있다. 주로 특성값을 벡터 공간에 맵핑 한후, 벡터간의 거리를 기반으로 계산하는 방법이 많이 사용되는데, 유클리디안 거리, 코사인 거리, 자카드 거리, 피어슨 상관 계수, 맨해튼 거리등이 있다. 여기서는 코사인 거리를 사용하여 문서간의 유사도를 측정한다.


<