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


Archive»


 

'클라우드 컴퓨팅 & NoSQL/도커 & 쿠버네티스'에 해당되는 글 62

  1. 2019.10.18 구글 GKE 네트워크 기본 보안
  2. 2019.10.10 효율적인 도커 이미지 만들기 #2 - 도커 레이어 캐슁을 통한 빌드/배포 속도 높이기
  3. 2019.10.08 효율적인 도커 이미지 만들기 #1 - 작은 도커 이미지
  4. 2019.09.18 VM 수준의 보안을 제공하는 gVisor 컨테이너 런타임 (1)
  5. 2019.09.17 쿠버네티스 CRI (Container Runtime Interface) & OCI (Open container initiative)
  6. 2019.09.01 [팁] Skaffold 에서 Kubernetes context 변경
  7. 2019.08.24 쿠버네티스 #28 - 쿠버네티스 애플리케이션을 위한 IDE 설정하기 - VS Code
  8. 2019.08.22 쿠버네티스 #27 - 쿠버네티스 애플리케이션을 위한 IDE 설정하기 - IntelliJ
  9. 2019.08.20 쿠버네티스 #26 - 오토스케일러
  10. 2019.08.20 쿠버네티스 멀티 클라우드/클러스터를 위한 Rancher
  11. 2019.08.15 쿠버네티스 #25 - Pod 스케쥴링 #3 리소스 부족 관리
  12. 2019.08.11 쿠버네티스 #24 - Pod 스케쥴링 #2 Affinity
  13. 2019.08.04 쿠버네티스 #23 - Pod 스케쥴링 #1
  14. 2019.07.04 쿠버네티스용 Continuous Deployment 툴인 Skaffold #2 (1)
  15. 2019.06.25 쿠버네티스용 Continuous Deployment 툴인 Skaffold
  16. 2019.06.23 쿠버네티스 패키지 매니저 Helm #2-5. Chart 배포 (Repository)
  17. 2019.06.20 쿠버네티스 패키지 매니저 Helm #2-4. Chart Hook
  18. 2019.06.16 쿠버네티스 패키지 매니저 Helm #2-3. Charts (디렉토리 구조)
  19. 2019.06.11 쿠버네티스 패키지 매니저 Helm #2-2. Chart (버전과 릴리즈)
  20. 2019.06.09 쿠버네티스 패키지 매니저 Helm #2-1. Chart
 


1. 클러스터는 Private IP 모드로 생성

이렇게 되면, Node는 External IP를 가지지 못한다. 즉 외부 접근을 막을 수 있다.

  • In bound : Pod로 들어오는 트래픽은 Node에  External IP가 없더라도, Service를 통해서 들어올 수 있다.
  • out bound : Cloud NAT를 설정하면 된다.
2. Master Node에 대한 접근 제안
Master authorized network 를 설정한후, authorized network에 master node를 사용할 (kubectl) 대역을 지정한다.


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

댓글을 달아 주세요

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

#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 리눅스 기반으로 되어 있는 이미지를 사용하는 것이 가장 좋다.

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

댓글을 달아 주세요

VM 수준의 보안을 제공하는 gVisor

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


Docker와 같은 일반적인 컨테이너 모델의 문제점은 Host Kernel을 공유하기 때문에, privilege escalation을 통해서 Host Kernel 을 접근해서 전체 시스템을 장악할 수 있다는 문제점이 있다. privilege escalation을 SeLinux나 컨테이너 정책으로 막는다고 하더라도, 버그등으로 인해서 Host Kernel 로의 접근을 허용할 수 있는 잠재적인 보안 위협이 있다.

컨테이너에 비해서 VM은 하드웨어 계층 부터 가상화를 해서, 전체를 Isolation 시키기 때문에, 보안적으로는 상대적으로 우세하지만, 전체적으로 무겁다는 단점을 가지고 있다.

이런 문제를 해결하기 위해서 VM과 컨테이너의 장점을 조합해서 만든 것이 gVisor 라는 오픈소스이다.


기본적으로 Host Kernel 위에서 KVM을 통해서 Kernel 을 Emulation한다. VM처럼 하드웨어 전체를 Emulate 하는 것이 아니라, Emulated 된 커널을 올릴 수 있도록 하고, 이 커널과 컨테이너는 User space에서 실행된다.

일반적인 컨테이너의 경우 컨테이너는 User space에서 실행되지만, system call을 통해서 Kernel space로 접근이 가능한데, gVisor의 경우에는 user space에 emulated 된 커널을 올려서, system call이 발생하더라도 user space를 벗어나지 않도록 해준다. 


아래 그림은 gVisor와 K8S의 관계 개념도인데, 쿠버네티스가 컨테이너를 생성 관리 하려면 컨테이너런 타임이 있어야 하는데,  gVisor의 runtime은 runsc이다. runsc는 OCI 규격을 준수하고 있기 때문에 쿠버네티스에서 기존의 컨테이너등과 호환이 가능하게 된다.   gVisor의 컨테이너는 컨테이너와 Sentry라는 가상화된 커널로 구성되며 이는 user space에서 기동된다. 


<출처 : https://www.youtube.com/embed/TQfc8OlB2sg


이러한 구조를 통해서 VM 수준의 Isolation을 제공함으로써 높은 보안을 제공한다. 

현재 Google Kubernetes Engine (이하 GKE)에서 옵션으로 활성화가 가능하며, Pod spec에 아래 그림과 같이 runtimeClassName 을 gvisor로 정해주면 된다.


apiVersion: apps/v1

kind: Deployment

metadata:

  name: httpd

  labels:

    app: httpd

spec:

  replicas: 1

  selector:

    matchLabels:

      app: httpd

  template:

    metadata:

      labels:

        app: httpd

    spec:

      runtimeClassName: gvisor

      containers:

      - name: httpd

        image: httpd


이렇게 하면 이 Pod는 gvisor를 통해서 isolation이 된다. 일반적인 애플리케이션인 java,node.js,ruby 나 redis나 mysql과 같은 범용 애플리케이션은 잘 동작하지만


gVisor는 높은 수준의 보안을 요구 하는 애플리케이션이나 특히 대외로 서비스 되는 Multi tenant application의 경우 높은 isolation 레벨을 제공해서, 다른 Pod 들이 영향을 받지 않도록 하는데 유용하게 사용할 수 있다. 


이러한 장점은 있지만 역시 만능은 아니고 제약 사항이 분명히 있다.

  • HostPath 볼륨 사용불가

  • Istio 사용불가

  • 일부 PSP 사용불가

  • Pod나 컨테이너 레벨 모니터링 불가

  • Kernel 의 특정 보안 모듈 secomp,Apparmor,Selinux 등 사용 불가

  • GPU 등 사용불가

자세한 제약 사항은 https://cloud.google.com/kubernetes-engine/docs/how-to/sandbox-pods#limitations 문서를 참고하기 바란다. 



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

댓글을 달아 주세요

  1. 조윤희 2019.09.24 08:22  댓글주소  수정/삭제  댓글쓰기

    GPU, TPU 지원되는 VM 이나, privilege 설정없이 TPU를 사용할수있는 docker는 없나요?

CRI & OCI


기본적으로 도커 기반의 쿠버네티스는 다음과 같은 구조로 작동을 했었다. Kubelet이 명령을 받으면, Docker runtime을 통해서 컨테이너를 생성하거나 삭제하는 것과 같은 생명 주기를 관리하는 구조를 가지고 있었다. 



그런데, Docker 이외에도 여러가지 컨테이너 기술이 나오면서 쿠버네티스에서 이런 다양한 컨테이너 런타임에 대한 지원 요건이 생기기 시작하였고, 다양한 컨테이너 런타임을 지원하기 위해서 그때 마다 Kubelet의 코드를 수정해야 하는 문제가 생겼다.


그래서, kubelet의 코드를 수정하지 않고, 다양한 컨테이너 런타임을 지원하기 위해서, kubelet과 컨테이너 런타임 사이의 인터페이스를 통일화하는 스펙이 등장하였는데, 이것이 CRI (Container Runtime Interface)이다. 컨테이너의 생성,삭제 등의 생명 주기를 관리하는 스펙으로 gRPC 기반의 API 스펙으로 되어 있고, 새로운 컨테이너 런타임은 CRI 스펙에 맞춰서 CRI 컴포넌트를 구현하면 되는 구조가 되었다. 


그래서, 컨테이너 런타임이 CRI 스펙에 맞춰서 구현이 되면, kubelet의 코드 변화 없이 새로운 컨테이너 런타임을 플러그인 구조로 추가할 수 있는 구조가 된것이다.  Docker의 경우에는 docker shim 이라는 CRI 인터페이스를 준수하는 구현체를 제공하고 있고, rkt 컨테이너의 경우는 rktlet 이라는 이름의 CRI 구현체를 제공하고 있다. 

그런데, 지원되는 컨테이너의 종류가 계속해서 늘어가고 있고, 그때마다, CRI를 다시 구현해야 하는 문제가 생김에 따라 컨테이너 런타임 자체를 표준화하고자 하는 노력이 있었는데, 그로 인해서 정해진 스펙이 OCI (Open Container Initiative) 이다. OCI 스펙을 맞춰서 구현된 컨테이너 런타임을 별도의 CRI 구현이 없이 OCI를 지원하는 CRI 구현체에 의해서 관리가 가능해진다. OCI 스펙에 따른 컨테이너 런타임을 관리하는 CRI 컴포넌트는 CRI-O 라는 컴포넌트로 구현되어 있다. 즉 OCI 스펙을 준수한다면, CRI-O를 통해서 kubelet으로 부터 명령을 받을 수 있는 구조가 된다. 



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

댓글을 달아 주세요

쿠버네티스 Skaffold에서는 하나의 Configuration에서 Profile을 통하여, 배포 파이프라인을 여러개 만들어서 다른 환경에 배포할 수 있고,

각 환경은 kubernetesContext를 이용해서, 쿠버네티스 클러스터를 고를 수 있다. 


build:
  artifacts:
  - image: gcr.io/k8s-skaffold/skaffold-example
deploy:
  kubectl:
    manifests:
    - k8s-pod
profiles:
- name: profile1
  activation:
    - env: MAGIC_VAR=42
- name: profile2
  activation:
    - env: MAGIC_VAR=1337
    - kubeContext: minikube
      command: dev


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

댓글을 달아 주세요



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

#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 모드로 맞춘후에, 아래 그림과 같이 오른쪽 녹색 디버깅 버튼을 누르면 된다. 



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




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

댓글을 달아 주세요



#26 쿠버네티스 오토 스케일러

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


쿠버네티스에서는 리소스 부족을 처리하기 위해서, 오토 스케일러를 사용할 수 있다.. 쿠버네티스는 용도에 따라 몇가지 다른 오토스케일러를 제공하는데 각각을 살펴보도록 하자

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: api-server

spec:

  targetRef:

    apiVersion: "extensions/v1beta1"

    kind:       Deployment

    name:       api-server

  updatePolicy:

    updateMode: "Auto"


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


<그림 VPA의 동작 원리>

 

Auto 모드의 경에는 VPA는 Pod의 리소스 사용량을 일정 주기 이상 모니터링 한 후에, 그 Metric을 기반으로, 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 가 추천되는 값이다.

Horizontal Pod Autoscaler (HPA)

VPA가 Pod의 리소스를 더 할당해서 스케일링을 하는 기능이라면, Horizontal Pod Autoscaler (이하 HPA)는 Pod의 수를 늘려서 스케일링을 하는 기능이다.


HPA의 동작 원리는 다음과 같다. Pod 에서 메트릭을 저장해서 메트릭 서버에 저장한다. CPU나 메모리와 같은 리소스 지표들이 해당한다. 지표들이 지정해놓은 임계점을 지나면 Pod의 수를 늘려서 스케일링을 한다.



<그림. Horizontal Pod Autoscaler의 개념>


HPA는 메트릭을 모니터링하고, 실제로 Pod의 수를 늘리거나 줄이는것은 ReplicaSet이나 Deployment에 의해서 컨트롤 된다. 

적용 방법을 살펴보자, 아래와 같이 YAML 파일을 이용해서 HPA 리소스를 생성할 수 있다. 


apiVersion: autoscaling/v2beta2

kind: HorizontalPodAutoscaler

metadata:

  name: api-server

  namespace: default

spec:

  scaleTargetRef:

    apiVersion: apps/v1

    kind: Deployment

    name: api-server

  minReplicas: 1

  maxReplicas: 10

  metrics:

  - type: Resource

    resource:

      name: cpu

      target:

        type: Utilization

        averageUtilization: 50


HPA 설정에는 HPA 이름, 그리고 적용하고자 하는 Deployment의 이름, min,max Pod수 그리고, 측정 메트릭이 정의된다. 위의 예제는 api-server라는 HPA 이름으로 api-server 라는 deployment에 HPA를 적용하였고, CPU 사용률이 50% 이상이 되면 최대 10개의 Pod까지 스케일 업을 하도록 한 설정이다.


HPA는 아직 VPA와 같이 사용할 수 없다. 개발/테스트 환경에서 VPA를 이용해서 적절 resource 양을 측정한 다음, 운영환경에서는 HPA 정도만 적용하는 것을 권장한다.  

Cloud Auto-scaler

앞서 살펴본 VPA와 HPA가 Pod의 리소스 양을 늘려주거나 Pod의 수를 조정해서 부하를 감당하는 Pod 기반의 오토 스케일러라면 Cloud Autoscaler (이하 CA), Node의 수를 조정하는 오토 스케일러이다. AWS, Azure, Google Cloud 등 클라우드 인프라와 연동해서 동작하도록 되어 있고, 각 클라우드 서비스 마다 설정 방법이나 동작 방식에 다소 차이가 있으니, 자세한 내용은 클라우드 벤더별 문서를 참고하기 바란다.


기본적인 동작 방법은 같은데, Pod를 생성할때, Node들의 리소스가 부족해서 Pod를 생성할 수 없으면, Pod 들은 생성되지 못하고 Pending status로 대기 상황이 된다. Pending status 된 Pod를 CA가 감지 하면, Node를 늘리도록 한다. CA는 Pending status가 있는지를 감지 하기 위해서 (디폴트로) 10초 단위로 Pending status가 있는 Pod가 있는지를 체크한다.


Node Autoscaling 과정에서 주의해야 하는 점은 scale up 보다는  Node 수를 줄이는 scale down 이다. 

CA는 10 초 단위로 scale up 이 필요한지를 체크한 후에, scale up이 필요하지 않으면, scale down 이 가능한지를 체크한다. 

Node의 리소스(CPU,Memory)의 request 총합이 node 물리 리소스의 일정량 (디폴트는 50%)보다 작으면 scale down 을 고려 한다. 이때 리소스의 현재 사용량을 기반으로 하지 않음에 주의하기 바란다. Request를 넘어서 limit  아래까지 리소스가 사용되더라도 CA는 Node에 배포된 Pod들의 request 총합을 기준으로 한다. 

Scale down은 scale down 대상 node의 Pod를 다른 node로 옮기고, scale down node를 삭제하는 방식으로 작동하는데,Scale down 조건이 되면, Node는 그 Node에서 돌고 있는 Pod들을 옮길 수 있는지를 먼저 확인한다. 


  • 컨트롤러 (ReplicationSet,Job 등)에 의해서 관리되지 않는 naked pod는 옮길 수 없다.

  • 로컬 디스크를 사용하는 Pod는 옮길 수 없다. 

  • Affinity,Taint 등의 조건에 의해서 옮길 수 없는 경우 

  • 만약 Pod를 CA에 의해서 이동하고 싶지 않은 경우, Pod를 “cluster-autoscaler.kubernetes.io/safe-to-evict:True” 옵션을 부여하여, CA에 의해서 Pod가 옮겨지는 것을 막을 수 있다.  

  • 그리고 뒤에서 소개하는 Pod Disruption Budget (PDB)에 따라서 Pod를 삭제를 조정한다.


조건에 부합하여 모든 Pod를 옮길 수 있다면, scale down node로 부터 해당 Pod를 삭제하고, 그 Pod 들을 다른 Node에 생성하고, scale down 대상 node를 삭제한다. 


경우에 따라 특정 node를 CA에 의해서 scale down을 하고 싶지 않은 경우가 있을 수 있는데, 그 경우에는 node 에 다음과 같은 레이블을 적용하면 해당 Node는 Scale down에서 제외된다. 

"cluster-autoscaler.kubernetes.io/scale-down-disabled": "true"



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

댓글을 달아 주세요



조대협 (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 스케쥴링과 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도 배포될 수 있도록 한다.

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

댓글을 달아 주세요

쿠버네티스를 위한 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

[node-6545db86c5-wkdc4 node] [nodemon] to restart at any time, enter `rs`

[node-6545db86c5-wkdc4 node] [nodemon] watching: *.*

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

[node-6545db86c5-wkdc4 node] Example app listening on port 3000!


태그를 생성해서 gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a 이라는 이름으로 컨테이너를 만들어서 쿠버네티스에 배포하고 Port Forwarding node-6545db86c5-wkdc4/node 3000 -> 3000 에서 보는 것과 같이 Pod  node-6545db86c5-wkdc4의 3000 번 포트로 로컬 포트 3000번을 포워딩 하였다. 


kubectl 명령어를 이용해서 Deployment, Pod 그리고 Service들이 제대로 배포 되었는지를 확인해보면 다음과 같다. 


%kubectl get deploy

NAME                    DESIRED CURRENT UP-TO-DATE   AVAILABLE AGE

node                    1 1 1         1 3m


%kubectl get svc

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

node                               LoadBalancer 10.23.254.227 35.187.196.76    3000:30917/TCP 3m


%kubectl get pod

NAME                                     READY STATUS RESTARTS AGE

node-6545db86c5-wkdc4                    1/1 Running 0 17m


모든 자원들이 제대로 올라온것을 확인했으면 해당 Pod에 localhost:3000 터널을 이용해서 접속해 보자. 



이 상태에서 src/index.js 내용을 에디터를 이용해서 변경해서 저장하면, skaffold 가 파일이 변경되었다는 것을 인지하고, 도커 컨테이너 빌드와 재 배포를 자동으로 수행한다. 아래는 index.js에서 “Hello World 2!”를 출력하도록 소스코드를 수정하여 저장한 후, 수정 내용이 반영된 결과이다. 


중지

개발을 끝내고 싶을 때는 실행중인 skaffold만 중지하면, 아래와 같이 개발을 위해서 생성했던 이미지와 쿠버네티스 리소스 (Deployment, Service, Pod)등을 클린업 해주기 때문에 깔끔하게 개발했던 환경을 정리할 수 있다. 


^CPruning images...

untagged image gcr.io/terrycho-personal/node-example@sha256:bccf087827dd536481974eb28465ce4ce69cf13121589e4a36264ef2279e9d1d

deleted image sha256:5b713462d7ac7ebb468a1f850fa470c34f7b3aaa91ecb9a98b768e892c7625af

deleted image sha256:164f2f7ad057816d45349ffc9e04c361018cb8ce135accdc437f9c7b0cec7460

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

untagged image gcr.io/terrycho-personal/node-example@sha256:608ceeae6724e84d30ea61fcfedbaf94aedd3fb6dfbf38c97d244af8c65cbe54

deleted image sha256:d13071d2094092d50236db037ac7e75efca479ed899ec09e3a9a0b61789d93da

deleted image sha256:f9ff5b561a440490345672fe892eeedcd28e05dc01c4675fea8cf5f465b87898

untagged image gcr.io/terrycho-personal/node-example:v0.32.0-28-g6bd1d50a-dirty

untagged image gcr.io/terrycho-personal/node-example@sha256:0ba775dee9f33dc74cea1158f2bc85190649b8102991d653bc73a8041c572600

deleted image sha256:18c830d66ee7c7d508e4edef2980bd786d039706e9271bd2f963a4ca27349950

deleted image sha256:98d168285f8df5c00d7890d1bb8fcc851e8a9d8f620c80f65c3f33aecb540d28

untagged image gcr.io/terrycho-personal/node-example@sha256:0bb49fc5b4ff6182b22fd1034129aed9d4ca5dde3f9ee2dd64ccd06d68d7c0f8

deleted image sha256:a63e7f73658f4f69d7a240f1d6c0fa9de6e773e0e10ab869d64d6aec37448675

deleted image sha256:7e59e5acb2511a5a5620cf552c40bdcba1613b94931fe2800def4c6c6ac6b2a6

deleted image sha256:f7a7217f7a60b23ee04cf50a9bac3524d45d2fd14b851feedd3dc6ffac8b953f

deleted image sha256:b3f0702d41a7df01932c3d3f0ab852ceae824d8acc14f70b7d38e694caa67dc1

deleted image sha256:457e460aae8d185410b47b0a62e027a7d09c7e876cdfaa0c06bcaf6069812974

deleted image sha256:f8e24fe06e6e2c04b5bf29f6eb748d99107781333dc229d03ff77865c1937f8c

deleted image sha256:3b7878ac4c42f541efb80af06415bec286254912f8c3a64e671e7f2f3808b91b

deleted image sha256:49ac353388c6c1e248ac41262ffef47a26ada4d28955e90308c575407f316c64

deleted image sha256:7c725818faaeb6db8d5650e037dc4fa0f38a95bc858ea396f729dfa9ccdc1e06

deleted image sha256:ea8f04b01ca5be4b4fda950557526c3a0707ebda06d0e9392501cba21cc17327

WARN[1877] builder cleanup: pruning images: Error: No such image: sha256:18c830d66ee7c7d508e4edef2980bd786d039706e9271bd2f963a4ca27349950 

Cleaning up...

deployment.apps "node" deleted

Cleanup complete in 2.677585866s

There is a new version (0.32.0) of Skaffold available. Download it at https://storage.googleapis.com/skaffold/releases/latest/skaffo


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

댓글을 달아 주세요

쿠버네티스 패키지 매니저 Helm

#2-5 Helm Chart 배포

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


Helm 차트를 작성했으면, 다른 사용자들이 사용하기 쉽게 차트를 차트 리포지토리 (Chart repository)에 배포할 수 있다. 

Helm 파일 패키징

파일을 배포하기 위해서는 먼저 차트 파일들을 *.tgz 파일 형태로 패키징해야 하는데, helm package 명령을 사용하면 된다. 

%helm package [차트 디렉토리] 

형태로 사용하면 된다. 앞의 예제인 helloworld 차트를 패키징 하려면 아래와 같은 명령을 사용하면 된다.

%helm package ./helloworld

Successfully packaged chart and saved it to: /home/terrychol/31.helm/helloworld-0.1.0.tgz


만약에 패키지된 파일에 대한 무결성을 보장하기 위해서(패키지된 파일이 변조되지 않음을 보장하는 방법) 패키지파일에 키로 사이닝을 하는 방법이 있다. helm package --sign … 옵션을 이용해서 사이닝을 한다. 패키지에 사이닝을 하면 *.prov 파일 (provenance file) 이 생성되고, 차트 패키지를 설치할때 helm install --verify 옵션을 이용하면 이 provenance 파일을 이용해서 파캐지의 무결성 (변조가 되었는지)을 확인한 후, 변조되지 않은 경우에만 설치를 한다. 

원리 자체는 PKI(비대칭키) 알고리즘을 이용해서 패키지에 사이닝 한후에, 차트를 인스톨할때 사이닝을 확인하여 패키지 변조 여부를 파악하는 방식이다. 

자세한 설정 방법은  https://helm.sh/docs/developing_charts/#helm-provenance-and-integrity

문서를 참고하기 바란다. 

Helm Chart repository server

패키징된 차트패키지 파일을 서버에 배포해야하는데, 서버는 일반적은 HTTP 서버면 모두 사용이 가능하다. github,일반 웹서버, AWS S3, Google Cloud Storage(aka GCS)등이 모두 가능한데, 디렉토리 구조면 repository server 구조에 맞춰서 저장해놓으면 된다.


리포지토리 서버의 디렉토리 구조는 다음과 같다.


charts/

  

  |- index.yaml

  

  |- alpine-0.1.2.tgz

  

  |- alpine-0.1.2.tgz.prov


  • *.tgz 파일은 차트 패키지 파일이고

  • *.prov 파일은 차트 패키지에 대한 provenance 파일이다.

  • 그리고 index.yaml에 리파지토리에 있는 패키지들에 대한 정보를 저장한다.


아래는 index.yaml 파일 샘플이다.


apiVersion: v1

entries:

  helloworld:

  - apiVersion: v1

    appVersion: "1.0"

    created: 2019-06-19T15:37:08.158097657+09:00

    description: A Helm chart for Kubernetes

    digest: f7fcd1078546939bd04b4f94282fb15b3d8d4c422e61b5b03b7e4061c1b61037

    name: helloworld

    urls:

    - http://127.0.0.1:8879/helloworld-0.1.0.tgz

    version: 0.1.0

  helloworld2:

  - apiVersion: v1

    appVersion: "1.0"

    created: 2019-06-19T15:37:08.158803299+09:00

    description: A Helm chart for Kubernetes

    digest: 69510159a58a1c5c228b6870546b67d852b09d299b36d4617bf9e9b971be01fd

    name: helloworld2

    urls:

    - http://127.0.0.1:8879/helloworld2-0.1.0.tgz

    version: 0.1.0

generated: 2019-06-19T15:37:08.15656402+09:00


helloworld와 helloworld2 가 포함되어 있고, 패키지 URL은 http://127.0.0.1:8879/ 이다.

간단하게 Helm 차트 리파지토리를 띄우는 방법은 패키지 (*.tgz)이 있는 디렉토리에서 helm serve 명령을 이용하면 디폴트로 현재 디렉토리에 있는 패키지들을 이용하여 index.yaml 파일을 자동으로 생성하고, 이를 8879 포트를 이용해서 서빙한다.

아래는 helm serve 명령을 이용해서 현재 디렉토리 “.”를 패키지 디렉토리로 해서 차트 리파지토리 서버를 기동한 결과이다.


%helm serve --repo-path .

Regenerating index. This may take a moment.

Now serving you on 127.0.0.1:8879



위의 명령을 사용하면 자동으로 현재 디렉토리에 있는 패키지 파일 (*.tgz)을 읽어서 index.yaml을 자동으로 생성해서 repository 서비스를 제공한다.

helm serve를 이용하는 것이 아니라 웹서버등을 이용할 경우에는 index.yaml을 별도로 생성해줘야 하는데, helm repo index 라는 명령을 이용하면 된다.


%helm repo index [디렉토리명]


을 실행하면, [디렉토리명]에 있는 helm 패키지 파일들에 대한 index.yaml을 생성한다. 이때 웹서버의 URL을 정해줄 수 있는데, 


%helm repo index [디렉토리명] --url [http or https URL for repository]


--url 옵션으로 웹서버의 URL을 주면, index.yaml에서 패키지 경로에 웹서버의 경로를 붙여준다.


%helm repo index . --url https://bwcho75.github.io/my-repo

apiVersion: v1

entries:

  helloworld:

  - apiVersion: v1

    appVersion: "1.0"

    created: 2019-06-19T18:16:29.31329401+09:00

    description: A Helm chart for Kubernetes

    digest: f7fcd1078546939bd04b4f94282fb15b3d8d4c422e61b5b03b7e4061c1b61037

    name: helloworld

    urls:

     https://bwcho75.github.io/my-repo/helloworld-0.1.0.tgz

    version: 0.1.0

  helloworld2:

  - apiVersion: v1

    appVersion: "1.0"

    created: 2019-06-19T18:16:29.314605303+09:00

    description: A Helm chart for Kubernetes

    digest: 69510159a58a1c5c228b6870546b67d852b09d299b36d4617bf9e9b971be01fd

    name: helloworld2

    urls:

     https://bwcho75.github.io/my-repo/helloworld2-0.1.0.tgz

    version: 0.1.0

generated: 2019-06-19T18:16:29.311471188+09:00


다음에 helm 클라이언트에서 이 repository를 사용하도록 하려면,이 repository를  리스트에 추가해야 한다. 

명령을 helm repo add [리파지토리 이름] [URL] 식으로 지정하면 된다.

리파지토리 이름은 사용자가 임의적으로 정하는 이름이고 URL은 Helm 리자지토리의 http URL 이다. 

아래는 myrepo라는 이름으로, http://localhost:8879 서버를 등록하는 방법이다. 

helm repo add myrepo http://localhost:8879

"myrepo" has been added to your repositories


팀내나 아니면 작은 시스템을 위한 Helm repository 라면, helm serv,git (or github) 또는 간단한 웹서버 정도로도 repository 운영이 가능하다. 새로운 차트의 등록은 위에 처럼  그러나 큰 규모로 운영을 하거나 외부에 까지 repository를 오픈할 경우에는 사용자 인증등 별도의 보안 기능이 있고, 매번 index.yaml을 재생성하는게 아니라, 추가 삭제할 수 있는 repository를 사용하는 것을 권장한다. 

ChartMuseum

Charmusem (https://chartmuseum.com) 은 오픈소스 Helm Chart Repository 서버이다. 인증 기능을 제공할 뿐만 아니라 파일 스토리지를 AWS S3, 구글 GCS등을 백앤드로 사용할 수 있다. 


기본 설치 및 사용은 도커로 패키징 되어있는 이미지를 사용하면 된다.

docker run --rm -it \

  -p 8080:8080 \

  -v $(pwd)/charts:/charts \

  -e DEBUG=true \

  -e STORAGE=local \

  -e STORAGE_LOCAL_ROOTDIR=/charts \

  chartmuseum/chartmuseum:v0.8.1


Helm repository 서버이외에도, Chartmuseum은 추가적으로 필요한 기능에 대해서 오픈소스로 제공하고 있다. 

이중에서 주의 깊게 볼만한것은 chartmuseum/ui 와 chartmuseum/helm-push 인데, ui는 chartmuseum 에 대한 웹 인터페이스를 제공한다.

<그림. Chartmuseum ui 웹 화면 >

Chartmuseum push는 CLI도구로, 로컬에 있는 Helm 차트 패키지를 Chartmuseum 에 설치할 수 있는 기능이다. Helm 클라이언트가 깔려 있는 로컬 환경(PC나 노트북)에 인스톨 해서 사용한다.

로컬환경에 설치를 한후에, 차트를 Chartmuseum repository에 차트를 저장하려면 

%helm push [차트디렉토리] [repository 서버명]

으로 실행하면 된다. [차트 디렉토리]는 차트 파일이 들어있는 디렉토리이고 [repository 서버명] 은 helm repo add로 등록한 repository 이다.  

%helm push ./helloworld chartmuseum

지금까지, Helm에 대해서 알아보았다. Helm 은 쿠버네티스를 사용할때, 같이 많이 사용되는 솔루션이고 특히 쿠버네티스에 애플리케이션 설정 및 배포 관점에서 매우 유용하다. 물론 전체 CI/CD 파이프라인을 모두 만들 수 는 없지만, Spinnaker나 Jenkins X 등의 툴과 함께 전체 CI/CD 파이프라인의 중요한 요소로서 사용된다. 



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

댓글을 달아 주세요

쿠버네티스 패키지 매니저 Helm

#2-4 Helm Chart Hook


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

Hook은 차트 설치나, 삭제와 같이 차트의 라이프 사이클 중에, 차크 개발자가 동작을 추가해줄 수 있도록 해주는 기능이다. mySQL을 차트로 설치한 후에, mySQL에 테이블을 생성하고 데이타를 로딩하거나, 차트로 Pod를 설치하기전에 Configmap이나 Secret 의 값을 세팅해놓는 것과 같은 작업을 예를 들 수 있다. 

등을 들 수 있다.


Hook 으로 실행되는 리소스는 따로 있는 것이 아니라, 기존의 쿠버네티스 리소스 (Job, Cron Job 등)에 metadata.annotations.”helm.sh/hook”  으로 태그를 달아주면, 이 리소스들은 Hook으로 정의되어 차트 인스톨 전후에 정해진 시점에 실행된다. 

아래는 차트 인스톨 전에  작업을 처리하도록 Hook을 정의한 예제이다. 


apiVersion: ...

kind: ....

metadata:

  annotations:

    "helm.sh/hook": "pre-install"

# ...


예를 들어 아래 그림과 같이 차트에, Deployment,Service,Job 등의 리소스를 차트에 정의해서 배포하고자 한다. 그중에, 인스톨 전과 후에 특정 작업을 Job을 이용해서 처리하고자 한다면, 그 Job들에 annotation으로, Helm Hook임을 명시해줘야 한다. 


<그림. Helm 차트내의 리소스와 Hook으로 정의된 리소스들>


이 차트를 실행하게 되면 다음과 같은 순서로 실행이 된다. 먼저 차트에 정의되어 있는 리소스 중에서 Hook 으로 정의된 리소스중, pre-install 로 정의된 리소스가 먼저 실행이 되고, 그 다음에 다른 리소스가 인스톨이 된다. 인스톨이 완료되고 나면 그후에 post-install 로 정의된 Hook이 실행되는 형태이다. 



<그림. Helm 차트에서 리소스들의 배포 순서>


Hook 종류

Hook 은 인스톨 전과 후 뿐만 아니라, 릴리즈를 삭제하거나 업그레이드 할때 등 다양한 시점에 Hook을 삽입할 수 있도록 정의되어 있다.  자세한 Hook 의 종류는 https://github.com/helm/helm/blob/master/docs/charts_hooks.md 문서를 참고하기 바란다. 정의된 Hook 중 몇가지를 보면 다음과 같다. 


  • pre-install: 리소스를 설치하기전에 실행된다. (정확히 이야기 하면 리소스를 설치하기 위한 템플릿이 렌더링 된 후에, 렌더링된 템플릿으로 리소스를 설치하기 전에 실행된다. )

  • post-install: Helm 차트에 의해서 리소스들이 모두 설치 된 후에 실행된다. 

  • pre-delete: 릴리즈의 리소스를 삭제할때, 삭제 전에 실행된다.

  • post-delete:릴리즈의 리소스를 삭제한 후에, 실행된다.  

  • pre-upgrade: 릴리즈를 업그레이드 하기 전,  ( 템플릿이 렌더링 된 후에) 리소스가 생성되기 바로 전에 실행된다.

  • post-upgrade: 릴리즈 업그레이드가 끝난 후에, 실행된다. 

  • pre-rollback: 릴리즈를 기존 버전으로 롤백 할때 실행된다. (템플릿이 렌더링 된 후에 )

  • post-rollback: 릴리즈에 대한 롤백이 완료된 후에 실행된다. 

  • crd-install:  CRD 리소스를 인스톨하는데, 다른 Hook이나 기타 모든 다른 태스크 보다 우선적으로 실행된다. 이 Hook은 다른 리소스에서 이 CRD를  참조하여사용할때 주로 사용된다. 

  • test-success: “helm test” 명령을 실행할때 수행되는데, 그 결과가 성공 (return code == 0)일때만 실행된다. 

  • test-failure: “helm test” 명령을 실행할때 수행되는데, 그 결과가 실패 (return code != 0)일때만 실행된다. 

Hook weight & policy

Hook에는 실행 시점을 정의하는 기능 뿐만 아니라, 여러개의 Hook이 정의되었을때 실행 순서를 정하거나 또는 Hook 실행이 끝났을때 Hook 리소스를 지울지 나둘지등의 정책을 결정하는 기능이 있다. 

Hook weight

Hook weight는 여러개의 Hook 이 있을때, Hook 들의 실행 순서를 정의하기 위해서 사용한다. 음/양수 모두를 사용할 수 있으며, 작은 수를 가지고 있는 Hook 부터 우선 실행된다.

Hook weight는 아래 그림과 같이 annotations 부분에 “helm.sh/hook-weight” 항목으로 지정할 수 있다. 

 annotations:

    "helm.sh/hook-weight": "5"

Hook delete policy

Hook 이 실행된 후에, Hook 리소스들을 지울지 말지를 설정할 수 있는데, Hook이 실행된 후에, Hook 리소스는 일반적으로는 남아 있다. 만약에 Hook 이 실행된 후에, Hook을 삭제 하고 싶으면 annotation에 “helm.sh/hook-delete-policy”에 아래와 같이 삭제 정책을 정하면 된다. 


annotations:

    "helm.sh/hook-delete-policy": hook-succeeded 

삭제 정책으로 사용할 수 있는 정책은 다음과 같다. 

  • "hook-succeeded" : Hook 이 성공적으로 실행이 되고 나면, 삭제하도록 한다. 

  • "hook-failed" : Hook 실행이 실패하였을 경우 삭제 한다. 

  • "before-hook-creation" : Hook 을 실행하기 전에, 기존의 Hook을 삭제하고 실행하도록 한다. 


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

댓글을 달아 주세요

쿠버네티스 패키지 매니저 Helm

#2-3. Charts (디렉토리 구조)

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

디렉토리 구조

Helm 차트의 디렉토리 구조는 다음과 같다. 직접 아래와 같은 디렉토리 구조에 파일을 각각 생성해도 되지만, 기본 템플릿을 helm create [차트명] 으로 생성할 수 있다.

아래는

%helm create mychart

명령으로 생성한 디렉토리의 구조이다.


mychart/

 Chart.yaml          # A YAML file containing information about the chart

 LICENSE             # OPTIONAL: A plain text file containing the license for the chart

 README.md           # OPTIONAL: A human-readable README file

 requirements.yaml   # OPTIONAL: A YAML file listing dependencies for the chart

 values.yaml         # The default configuration values for this chart

 charts/             # A directory containing any charts upon which this chart depends.

 templates/          # A directory of templates that, when combined with values,

                     # will generate valid Kubernetes manifest files.

 templates/NOTES.txt # OPTIONAL: A plain text file containing short usage notes

Chart.yaml 파일

Chart.yaml은 차트에 대한 기본적인 메타 정보를 정의 한다.

apiVersion: The chart API version, always "v1" (required)

name: The name of the chart (required)

version: A SemVer 2 version (required)

kubeVersion: A SemVer range of compatible Kubernetes versions (optional)

description: A single-sentence description of this project (optional)

keywords:

 - A list of keywords about this project (optional)

home: The URL of this project's home page (optional)

sources:

 - A list of URLs to source code for this project (optional)

maintainers: # (optional)

 - name: The maintainer's name (required for each maintainer)

   email: The maintainer's email (optional for each maintainer)

   url: A URL for the maintainer (optional for each maintainer)

engine: gotpl # The name of the template engine (optional, defaults to gotpl)

icon: A URL to an SVG or PNG image to be used as an icon (optional).

appVersion: The version of the app that this contains (optional). This needn't be SemVer.

deprecated: Whether this chart is deprecated (optional, boolean)

tillerVersion: The version of Tiller that this chart requires. This should be expressed as a SemVer range: ">2.0.0" (optional)

차트의 이름이나 제작자등이 들어가는데, 몇가지 중요한 필드들을 살펴보자.

  • name은 Helm 차트의 이름을 정의한다.

  • version을 정의하는 것은 두가지 필드가 있는데, version 필드가 있고, appversion 이라는 필드가 따로 있다. version 필드는 helm 차트 자체의 버전을 정의하고, appversion은 이 차트를 통해서 배포 되는 애플리케이션의 버전을 정의한다. 예를 들어 helm 차트를 처음 만들었으면, helm 차트 버전은 1.0.0이 되고, helm chart를 통해서 채팅 애플리케이션 1.2를 배포한다면 appVersion은 1.2가 된다. 버전에 대한 네이밍은 semver (https://semver.org/lang/ko/)을 따르도록 한다.

License 및 README 파일

License 파일에는 이 Helm 차트의 라이센스 그리고,README에는 간략한 설명들을 적어 놓는다.

requirement.yaml 파일

Requirement.yaml에는 이 helm 패키지를 설치하기 위해서 필요한 다른 차트들의 목록을 기술한다. 이렇게 기술된 차트들은 이 차트가 설치되기 전에 자동으로 설치된다.

requirement.yaml 파일의 구조는 다음과 같다.


dependencies:

 - name: apache

   version: 1.2.3

   repository: http://example.com/charts

 - name: mysql

   version: 3.2.1

   repository: http://another.example.com/charts


Requirement.yaml에는 이 차트에 대한 의존성을 정의하는데, 필요한 차트의 이름과 버전 그리고, 그 차트가 저장된 리파지토리 경로를 정의한다.

위의 예제를 보면 apache의 1.2.3 버전의 차트가 필요하고, 이 차트는 http://example.com/charts에 저장되어 있음을 알 수 있다. 리파지토리에 대해서는 뒤에서 다시 한번 자세히 설명하도록 한다.


참고로 의존성에 정의된 차트들을 모두 설치하게할 수 도 있지만, tag나 condition을 사용하여, 조건에 따라 설치를 조정할 수 있다. 예를 들어 node.js API서버와 MySQL로 되어 있는 2-tier 애플리케이션이 있을때, MySQL을 같이 설치하도록 의존성을 정해놓을 수 있지만, 이미 MySQL이 쿠버네티스 클러스터내에 설치되어 있을 경우에는 그것을 재활용하면 되기 때문에, 옵션에 따라서 MySQL을 설치하거나 설치하지 않도록 할 수 있다.

charts/ 디렉토리

의존성이 필요한 차트들을 requirement.yaml 에 정의해놓는 경우, requirement.yaml에 있는 경로에서 다운로드 받아서 설치하는데, 다운로드를 받게하지 않고,차트를 배포할때 아예 같이 묶어서 배포하고 싶을 경우에는  charts 디렉토리에 차트들의 파일을 저장해놓으면, 차트를 배포할때 charts 디렉토리에 있는 차트를 먼저 배포하게 된다.

templates/ 디렉토리

Template는 앞의 예제를 통해서  설명했듯이, Helm 차트의 탬플릿을 정의한다.  

values.yaml 파일

values.yaml에는 템플릿에 사용될 value 값들을 저장한다



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

댓글을 달아 주세요

쿠버네티스 패키지 매니저 HELM

#2-2. Chart 버전과 릴리즈

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

업그레이드와 롤백

Chart로 의해서 설치된 리소스들을 쿠버네티스에서 릴리즈라고 한다.

예를 들어 같은 차트로 MySQL을 쿠버네티스 클러스터 내에 여러번 설치 할 수 있다.  각각 설치된 MySQL들을 릴리즈라고 하고, 릴리즈에 설정이 변경된 경우에, 차트를 수정해서 변경을 반영할 수 있다. 변경이 반영될때 마다 새로운 버전이 생성된다.


처음 helm install로 설치를 할때 --name 옵션으로 저장한 설치 이름이 릴리즈 명이 되고, 이 릴리즈를 업데이트 하고 싶으면 helm upgrade {Helm 릴리즈명} {차트 디렉토리} 를 실행하면, 해당 릴리즈를 업데이트하고, 새로운 버전을 생성한다.

앞의 예제의 values.yaml 에서 replicaCount: 3 를 변경한 후, upgrade를 해보자

%helm upgrade helloworld ./helloworld/

명령을 실행하면 다음과 같이 기존 배포가 업데이트 된다.


Release "helloworld" has been upgraded. Happy Helming!

LAST DEPLOYED: Sun Jun  9 20:58:41 2019

NAMESPACE: default

STATUS: DEPLOYED


RESOURCES:

==> v1/Service

NAME            AGE

helloworld-svc  2d


==> v1beta2/Deployment

helloworld-deployment  2d


==> v1/Pod(related)


NAME                                    READY STATUS RESTARTS AGE

helloworld-deployment-696fc568f9-2dkpm  0/1 ContainerCreating 0 0s

helloworld-deployment-696fc568f9-fqqg8  1/1 Running 0 2d

helloworld-deployment-696fc568f9-r8qct  1/1 Running 0 2d


그리고 위와 같이 Deployment pod가 3개로 늘어난것을 확인할 수 있다.

업그레이드 한후 버전을 확인하려면  helm history {릴리즈 이름} 을 사용하면 되는데,

% helm history helloworld

를 실행하면 아래와 같이 2번 버전이 새로 생긴것을 확인할 수 있다.


REVISION UPDATED                  STATUS     CHART            DESCRIPTION     

1        Fri Jun  7 23:24:34 2019 SUPERSEDED helloworld-0.1.0 Install complete

2        Sun Jun  9 20:58:41 2019 DEPLOYED   helloworld-0.1.0 Upgrade complete


만약 예전 버전으로 돌리고 싶으면 rollback 명령을 사용하면 되는데, helm rollback {릴리즈 이름} {릴리즈 버전} 으로 실행하면 된다. helm rollback helloworld 1 는 helloworld 릴리즈를 1 버전으로 롤백 하는 명령어 이다.  명령어를 실행해보면 다음과 같이 롤백이 완료되는 것을 확인할 수 있고,

% helm rollback helloworld 1

Rollback was a success! Happy Helming!


helm history로 해당 릴리즈의 버전을 확인해보면, 3번 버전에서 1번으로 롤백을 한것을 확인할 수 있다.

%helm history helloworld

REVISION UPDATED                  STATUS     CHART            DESCRIPTION     

1        Fri Jun  7 18:24:34 2019 SUPERSEDED helloworld-0.1.0 Install complete

2        Sun Jun  9 20:58:41 2019 SUPERSEDED helloworld-0.1.0 Upgrade complete

3        Sun Jun  9 21:43:48 2019 DEPLOYED   helloworld-0.1.0 Rollback to 1


릴리즈

앞에서도 설명했듯이 차트 하나로 같은 클러스터에 같은 애플리케이션을 여러개를 설치할 수 있다. MySQL이나 Redis 메모리 서버들은 애플리케이션이 아니기 때문에  같은 이미지로 여러개의 릴리즈를 설치할 수 있다.

그런데 앞의 스크립트로 helloworld 차트를 한번 더 설치하면 에러가 날것이다. 이유는 차트에 정의된 Service와 Deployment 리소스의 이름이 동일하기 때문이다.

templates/helloworld.yaml 파일에서 Deployment 이름 정의 부분을 보면, 이름을 {{.Value.name}}을 사용하도록 하였다.


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: {{ .Values.name }}-deployment

:


그래서 values.yaml에서 name을 변경하지 않는 이상, Deployment는 같은 이름을 가지게 된다.

이 문제를 해결하기 위해서 리소스들의 이름을 릴리즈 이름을 사용하도록 하면 된다. 릴리즈 이름은 {{ .Release.Name}} 을 사용하면, 릴리즈 이름을 리소스 이름으로 사용할 수 있다. helloworld.yaml을 아래와 같이 수정하면 된다.


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: {{ .Release.Name }}

spec:


그런데, 이렇게 설치가 된 리소스들이 어떤 차트에 의해서 설치된 것인지 구별이 잘 안될 수 있기 때문에, 좀더 좋은 방법은 리소스 이름을 “차트 이름-릴리즈 이름" 형태로 하는 것이 좋다. 차트 이름은 {{ .Chart.Name }} 을 사용하면 된다. 아래는 “차트 이름-릴리즈 이름" 형태로 정의한 예제이다.


apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: {{ .Chart.Name }}-{{ .Release.Name }}


helm template 명령을 이용해서 테스트를 해보자. 같은 예제를  helloworld2 디렉토리에 새롭게 만들었다. (그래서 아래 명령을 보면 ./helloworld2 디렉토리를 차트 디렉토리로 실행하였다.) 그리고 릴리즈 이름을 --name을 이용해서 myrelease로 지정하였다.


% helm template --name myrelease ./helloworld2

---

# Source: helloworld2/templates/helloworld.yaml

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: helloworld2-myrelease

spec:

 replicas: 3

:


metadata:

 name: helloworld-deployment

spec:

 replicas: 10

 minReadySeconds: 5

삭제

그리고 설치된 차트는 간단하게 helm delete 명령으로 삭제가 가능하다.

%helm delete helloworld

release "helloworld" deleted



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

댓글을 달아 주세요

쿠버네티스 패키지 매니저 HELM

#2-1 .Chart
조대협 http://bcho.tistory.com

Helm Chart

차트는 helm의 패키지 포맷으로, 하나의 애플리케이션을 설치하기 위한 파일들로 구성되어 있다. 예를 들어 tomcat을 설치하기 위한 쿠버네티스의 pod,service,deployment를 위한 YAML 파일등을 포함한다.

템플릿과 밸류

Helm 은 기본적으로 템플릿의 개념을 사용한다. 템플릿 파일을 만들어놓은 후에, 밸류 값을 채워 넣어서 쿠버네티스 리소스를 정의한 YAML 파일을 생성한다. 예제를 살펴보자

Helm 은 기본적으로 템플릿의 개념을 사용한다. 템플릿 파일을 만들어놓은 후에, 밸류 값을 채워 넣어서 쿠버네티스 리소스를 정의한 YAML 파일을 생성한다. 예제를 살펴보자.


먼저 templates/helloworld.yaml 파일을 정의한다.

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: {{ .Values.name }}-deployment

spec:

 replicas: {{ .Values.replicaCount }}

 minReadySeconds: 5

 selector:

   matchLabels:

     app: {{ .Values.name }}

 template:

   metadata:

     name: {{ .Values.name }}-pod

     labels:

       app: {{ .Values.name }}

   spec:

     containers:

     - name: {{ .Values.name }}

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

       imagePullPolicy: Always

       ports:

       - containerPort: 8080

---

apiVersion: v1

kind: Service

metadata:

 name: {{ .Values.name }}-svc

spec:

 selector:

   app: {{ .Values.name }}

 ports:

   - name: http

     port: 80

     protocol: TCP

     targetPort: 8080

 type: LoadBalancer


그리고 ./values.yaml 파일을 아래와 같이 정의한다.


name: "helloworld"

replicaCount: 3


템플릿은 값을 채울 수 있는 말 그대로 템플릿이고, Value의 값을 이용해서 값을 채운다.

이를 개념적으로 표현해보면 다음과 같은 형태가 된다.



좌측은 템플릿 파일이고, 템플릿에서 밸류값은 별도의 파일에 정의한다.

정의한 키/밸류 형식으로 name:”helloworld”로 정의하였고, replicaCount: 2로 정의하였다

그리고 이 밸류값을 불러들이기 위해서는 {{.Value.키이름}} 식으로 템플릿내에 정의한다.

위의 예제에서 보면 name등에 {{.Value.name}} 으로 정의하였고, replicas 수는 {{ .Value.replicaCount }} 로 정의한다.

이렇게 정의된 템플릿에 밸류 내용을 정의하면 오른쪽 처럼 YAML 파일을 생성할 수 있다.


외부에서 Value를 받는 방법

Value값을 values,yaml에 지정해놨지만, 설치에 따라서 각 값을 변경하고 싶은 경우가 있다. 예를 들어 replicaCount를 3이 아니라 10으로 변경하고 싶을 경우에는 values.yaml 파일을 일일이 에디트 해야 한다. 나중에, 차트를 차트 리파지토리에 등록하기 위해서는 압축된 파일 형태를 사용하는데, 이 경우에는 그러면 차트 압축 파일을 다운로드 받은 후에 압축을 풀고나서, 내용을 수정하고 설치에 사용해야 하는 불편함이 있다.

이렇게 일일이 수정하지 않고 CLI에서 변경하고 싶은 인자만 간단하게 지정할 수 있는 방법이 없을까?

helm install 이나 upgrade시에  --set 옵션을 사용하면 된다. 예를 들어 values.yaml에 정의된 replicaCount를 10으로 변경하고자 하면 다음과 같이 하면 된다. %helm template --name myrelease --set replicaCount=10 ./helloworld


아래는 template 명령을 이용해서 테스트한 결과이다.   replicas가 10으로 변경된것을 볼 수 있다.

---

# Source: helloworld/templates/helloworld.yaml

apiVersion: apps/v1beta2

kind: Deployment


만약 설정값이 많아서 --set 을 이용해서 parameter로 넘기기가 어렵다면, 필요한 변수만 파일로 만들어서 넘길 수 있다.

예를 들어 myvalue.yaml 에 아래와 같이 name만 “fromValuefile” 로 정의를 한후에,


name: "fromValuefile"


helm install에서 -f 옵션으로 value 파일을 지정할 수 있다.

%helm install -f myvalues.yaml --name newrelease --dry-run --debug ./helloworld


결과를 보면 다음과 같다.

# Source: helloworld/templates/helloworld.yaml

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: fromValuefile-deployment

spec:

 replicas: 3

:

myvalues.yaml에 지정한 name에 의해서 Deployment name이  fromValuefile-deployment로 변경되고, replica 수는 원래 values.yaml에 지정한대로 3을 사용한 것을 확인할 수 있다.


디렉토리 구조

개념을 이해 하였으면, 파일을 어디에 저장하는지 디렉토리 구조를 살펴보자, helloworld라는 차트를 만들 것인데, helloworld라는 디렉토리를 만든다.

그리고 그 아래 템플릿들은 templates 라는 디렉토리에 yaml 로 정의한다. 여기에 채울 value들은 helloworld/values.yaml 파일내에 저장한다.



그리고 helloworlds/Chart.yaml 이라는 파일을 생성해야 하는데, 이 파일에는 이 헬름 차트에 대한 버전이나 작성자, 차트 이름과 같은 메타 정보를 정의한다.


다음은 Chart.yaml 의 내용이다.

apiVersion: v1

appVersion: "1.0"

description: A Helm chart for Kubernetes

name: helloworld

version: 0.1.0

테스트(검증)

헬름 차트 작성이 끝났으면 제대로 작동하는지 검증을 해볼 수 있다. 먼저 문법적인 오류가 없는지 확인 하는 명령은 helm lint 명령을 사용하면 된다. 명령어 실행은 차트 디렉토리 위에서 해야 한다. 이 예제에서는 ../helloworld 디렉토리가 된다.

실행하면 다음과 같은 결과를 볼 수 있다.


%helm linit ./helloworld

==> Linting ./helloworld/

[INFO] Chart.yaml: icon is recommended


1 chart(s) linted, no failures


문법적인 오류가 없는지 점검을 해준다. 다음으로 탬플릿에 밸류가 제대로 적용되서 원하는 YAML을 제대로 생성해나가는지 검증해야 하는데, helm template이라는 명령어를 사용하면 된다. helm lint 명령과 마찬가지로 차트가 저장된 디렉토리의 상위 디렉토리 (../helloworld)에서 실행한다.

다음은 helm template 명령을 실행한 결과이다.


%helm template ./helloworld

# Source: helloworld/templates/helloworld.yaml

apiVersion: apps/v1beta2

kind: Deployment

metadata:

 name: helloworld-deployment

spec:

 replicas: 2

 minReadySeconds: 5

 selector:

   matchLabels:

     app: helloworld

 template:

   metadata:

     name: helloworld-pod

     labels:

       app: helloworld

   spec:

     containers:

     - name: helloworld

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

       imagePullPolicy: Always

       ports:

       - containerPort: 8080


내용 처럼 Value.name과  Value.replicaCount 값이 채워져서 Deployment 리소스에 대한 YAML 파일이 생성된것을 확인할 수 있다.

helm template 명령을 helm 클라이언트에서 tiller 서버 접속없이 template에 값이 채워지는지만 테스트를 하는 것이고, tiller 서버에 연결해서 테스트 할 경우에는 helm install --dry-run 옵션을 사용한다.  그리고, 내용을 확인하기 위해서 --debug 옵션을 추가한다.

helm install --name myrelease --dry-run --debug ./helloworld 이렇게 명령어를 사용하면 되는데, 장점은, 실제 서버에 연결해서 같은 릴리즈 버전이 있는지 등의 체크를 해주기 때문에, 실수를 막을 수 있다.

아래는 같은 릴리즈 버전으로 설치하는 것을 --dry-run으로 테스트한 결과이다.


% helm install --name myrelease --dry-run --debug ./helloworld

[debug] Created tunnel using local port: '56274'


[debug] SERVER: "127.0.0.1:56274"


[debug] Original chart version: ""

[debug] CHART PATH: /Users/terrycho/dev/workspace/kube/kubernetes-tutorial/31.helm/helloworld


Error: a release named myrelease already exists.

Run: helm ls --all myrelease; to check the status of the release

Or run: helm del --purge myrelease; to delete it


차트를 같은 이름(릴리즈명/뒤에서 다시 설명함)이 있기 때문에 설치할 수 없다 것을 테스트 단계에서 미리 확인할 수 있다.

실제 설치

그러면 생성한 헬름 차트를 이용해서 쿠버네티스에 리소스를 실제로 설치해보자. 설치 방법은 차트가 있는 디렉토리에서 helm install  명령을 이용해서 인스톨을 하면 된다. 이때 --name이라는 이름으로 설치된 차트 인스턴스의 이름을 설정해줘야 한다. 만약에 이름을 정해주지 않으면 임의의 이름이 자동으로 생성되어 사용된다.


%helm install --name helloworld ./helloworld/


명령을 실행하면 아래와 같이 deployment가 생성되는 것을 확인할 수 있다. 예제에서 설명은 하지 않았지만, 테스트에 사용된 코드에는 Service를 배포하는 부분이 함께 포함되어 있기 때문에 아래 실행결과를 보면 Service까지 같이 생성된것을 확인할 수 있다.


NAME:   helloworld

LAST DEPLOYED: Fri Jun  7 23:01:44 2019

NAMESPACE: default

STATUS: DEPLOYED


RESOURCES:

==> v1beta2/Deployment

NAME                   AGE

helloworld-deployment  1s


==> v1/Pod(related)


NAME                                    READY STATUS RESTARTS AGE

helloworld-deployment-696fc568f9-mc6mz  0/1 ContainerCreating 0 0s

helloworld-deployment-696fc568f9-nsw48  0/1 ContainerCreating 0 0s


==> v1/Service


NAME            AGE

helloworld-svc  1s


설치가 완료되었으면 리소스가 제대로 생성되었는지 확인을 해보기 위해서 kubectl get deploy 명령을 실행한다.


%kubectl get deploy

kubectl get deploy

NAME                    DESIRED CURRENT UP-TO-DATE   AVAILABLE AGE

helloworld-deployment   2 2 2         2 10m


위와 같이 Deployment 리소스가 생성 된것을 확인할 수 있다.

헬름을 통해서 설치된 차트들은 helm list 명령을 이용해서, 설치 상태를 확인할 수 있다.

helm list 명령을 실행해보면 아래와 같이 helloworld 차트가 설치된것을 확인할 수 있다.


%helm list

NAME               REVISION UPDATED                  STATUS   CHART            APP VERSION NAMESPACE

helloworld         1        Fri Jun  7 23:01:44 2019 DEPLOYED helloworld-0.1.0 1.0  




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

댓글을 달아 주세요