인프라/Docker&Kubernetes 2019. 11. 19. 21:55

 

이번 포스팅은 kubernetes에 대해 다루어본다. 사실 쿠버네티스를 다루기 위해서는 docker(도커)에 대한 지식이 필요하지만 여기에서는 다루지 않는다. 그렇다면 쿠버네티스란 무엇인가?

 

쿠버네티스란?
쿠버네티스는 컨테이너 운영을 자동화하기 위한 컨테이너 오케스트레이션 도구이다. 많은 수의 컨테이너를 협조적으로 연동시키기 위한 통합 시스템이며 이 컨테이너를 다루기 위한 API 및 명령행 도구등이 함께 제공된다.
컨테이너를 이용한 애플리케이션 배포 외에도 다양한 운영 관리 업무를 자동화할 수 있다. 도커 호스트 관리, 서버 리소스의 여유를 고려한 컨테이너 배치, 스케일링, 여러 개의 컨테이너 그룹에 대한 로드 밸런싱, 헬스 체크 등의 기능을 갖추고 있다.

 

쿠버네티스 이외에도 도커 컴포즈, 스웜, 스택등을 이용하여 컨테이너 오케스트레이션을 다룰 수 있지만, 쿠버네티스는 더 충실한 기능을 갖춘 컨테이너 오케스트레이션 시스템이자 사실상 가장 표준으로 자리잡은 도구라고 볼 수 있다. 이번 포스팅에서는 완벽한 클러스터 구성을 다루어보지는 못하지만 간단히 로컬에서 예제를 다루어보고 이후에 진짜 여러 머신에서 클러스터 구성하는 방법을 다루어볼 것이다.

 

로컬 PC에서 쿠버네티스 실행

사실 실제 프러덕 환경에서는 사용하기 힘든 방법이지만, 간단히 쿠버네티스가 무엇인지 맛보기 위해 로컬 환경에서 쿠버네티스를 사용해본다. 

 

 

모든 환경은 Mac OS 환경에서 진행한다. 우선 각 PC에 설치된 도커 Preference에 들어가 위와 같이 Enable Kubernetes를 체크해준후 필요한 패키지들을 Install 한다.

 

kubectl 설치

kubectl은 쿠버네티스를 다루기 위한 명령행 도구이다. 로컬 환경이나 매니지드 환경(GKE등) 모두에서 사용할 수 있다.

 

brew install kubernetes-cli

 

kubectl이 잘 설치되었는지 확인해보자.

 

> kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2", GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean", BuildDate:"2019-10-15T19:18:23Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.8", GitCommit:"211047e9a1922595eaa3a1127ed365e9299a6c23", GitTreeState:"clean", BuildDate:"2019-10-15T12:02:12Z", GoVersion:"go1.12.10", Compiler:"gc", Platform:"linux/amd64"}

 

kubernetes dashboard 설치

대시보드는 쿠버네티스에 배포된 컨테이너 등에 대한 정보를 한눈에 보여주는 관리도구이다.

 

 

> kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-beta4/aio/deploy/recommended.yaml
> kubectl proxy

 

여기까지 모두 따라왔다면 아래 url로 접속가능하다.

 

http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login

 

접속 후에는 아마 아래와 같은 팝업이 보일 것이다. 2가지 방법의 접속이 있지만 우리는 토큰을 이용한 방법에 대해 다룬다.

 

 

> kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user | awk '{print $1}')

...
Data
====
ca.crt:     1025 bytes
namespace:  20 bytes
token:      eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZC10b2tlbi00aDU0eiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6IjczYWNkOGEzLTBhYjctMTFlYS1hMjRkLTAyNTAwMDAwMDAwMSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDprdWJlcm5ldGVzLWRhc2hib2FyZCJ9.JtykwdSsrqSPb45KieBxoLTgZ-EK2PPTsdsDHr-xJFmFqbMwduui9uQIJH0gFW53iLw_ySb6FpSKRoDNuKbNO-gs89DnGStfogu1ldOS4X4xxZAPIq3za-83OpBRNLGqG8nvPsHSYHPWhJ-vIHxImchlFv0xZDAQJklpIMWX2oQJMsBOkp688RnZ5xCm16YgqIFPCd712hfMufmHFzcdtd7U-Ma3fK6Jkl_FG7E_ee3o3E8kp3_HPdCcQkfMQwPtnvIGf21gmXLisGUsI12J-xdYt8ElmlU7wav0BjB7T-PK4LMUP_KmYytWTnpJL-okrU83wbOoblOyZitpNNHijQ

 

위와 같은 명령을 통해 쿠버네티스 대시보드 접속을 위한 토큰 값을 얻을 수 있다. 토큰 값을 복사해 접속해보자 .

 

 

쿠버네티스의 주요 개념

쿠버네티스로 실행하는 애플리케이션은 애플리케이션을 구성하는 다양한 리소스가 함께 연동해 동작한다. 여기서 말하는 쿠버네티스의 리소스란 애플리케이션을 구성하는 부품과 같은 것으로 앞으로 설명할 노드, 네임스페이스, 파드 등을 가리킨다.

 

쿠버네티스 클러스터와 노드

쿠버네티스 클러스터는 쿠버네티스의 여러 리소스를 관리하기 위한 집합체를 말한다. 여타 엘라스틱서치, 레디스 등 많은 미들웨어에서 사용하는 클러스터라는 용어와 크게 다르지 않다. 

 

쿠버네티스 리소스 중에서 가장 큰 개념은 노드(node)이다. 노드는 클러스터의 관리 대상으로 등록된 도커 호스트로, 도커 컨테이너가 배치되는 대상이다. 그리고 쿠버네티스 클러스터 전체를 관리하는 서버인 마스터가 적어도 하나 이상 있어야한다. 여기서 하나 이상이라는 말은 클러스터가 작동하기 위한 최소 조건이지만 실제 프러덕 환경에서는 절대 하나로 클러스터를 구성하지 않는다. 최소 3개 이상의 마스터 노드를 갖는 것이 좋다.

 

 

쿠버네티스는 노드의 리소스 사용 현황 및 배치 전략을 근거로 컨테이너를 적절히 배치한다. 다시 말해 클러스터에 배치된 노드의 수, 노드의 사양 등에 따라 배치할 수 있는 컨테이너 수가 결정된다는 뜻이다.

 

마스터를 구성하는 관리 컴포넌트

쿠버네티스의 마스터 노드에 배포되는 관리 컴포넌트에는 다음과 같은 것이 있다.

 

컴포넌트 역할
kube-apiserver 쿠버네티스 API를 노출하는 컴포넌트이다. kubectl로부터 리소스를 조작하라는 지시를 받는다.
etcd 고가용성을 갖춘 분산 키-값 스토어이다. 쿠버네티스 클러스터의 백킹 스토어로 사용된다.
kube-scheduler 노드를 모니터링하고 컨테이너를 배치할 적절한 노드를 선택한다.
kube-controller-manager 리소스를 제어하는 컨트롤러를 실행한다.

 

 

네임스페이스

쿠버네티스는 클러스터 안에 가상 클러스터를 또 다시 만들 수 있다. 이 클러스터 안의 가상 클러스터를 네임스페이스라고 한다. 클러스터를 처음 구축하면 default, docker, kube-public, kube-system의 네임스페이스 4개가 이미 만들어져 있다. kubectl get namespace 명령으로 현재 클러스터 안에 존재하는 네임스페이스의 목록을 확인할 수 있다.

 

> kubectl get namespace
NAME                   STATUS   AGE
default                Active   42m
docker                 Active   41m
kube-node-lease        Active   42m
kube-public            Active   42m
kube-system            Active   42m
kubernetes-dashboard   Active   25m

 

네임스페이스가 위에 설명에서는 클러스터 안의 가상 클러스터라고 설명했다. 사실 이해가 힘들수 있다. 쉽게 전체 클러스터에서 리소스의 구분 용도라고 생각해도 좋을 듯하다. 즉, 전체 클러스터에서 특정 이름으로 클러스터의 영역을 구분하는 것이다.

 

파드(pod)

파드는 컨테이너가 모인 집합체의 단위로, 적어도 하나 이상의 컨테이너로 이루어진다. 여기서 말하는 컨테이너는 도커 컨테이너를 이야기한다. 쿠버네티스를 도커와 함께 사용한다면 파드는 컨테이너 하나 혹은 컨테이너의 집합체가 된다.

 

쿠버네티스에서는 결합이 강한 컨테이너를 파드로 묶어 일괄 배포한다.(ex spring web app + nginx) 

 

 

또한 이러한 팟은 노드에 배치된다.

 

 

한 팟 안의 컨테이너는 모두 같은 노드에 배치된다. 다시 말해, 팟 하나가 여러 노드에 걸쳐 배치될 수는 없다.

 

그렇다면 가장 먼저 고민되는 부분은 '팟의 적절한 크기는 어느 정도인가'가 될 것이다. 보통 리버스 프록시 역할을 할 Nginx와 그 뒤에 위치할 애플리케이션 컨테이너를 함께 팟으로 묶는 구성이 일반적이다. 또한 예거와 같은 로그와 관련된 서버는 애플리케이션 컨테이너의 사이드카로 많이 팟을 구성한다.

 

또 함께 배포해야 정합성을 유지할 수 있는 컨테이너 등에도 해당 컨테이너를 같은 팟으로 묶어두는 전략이 유용하다. 

 

파드(pod) 생성 및 배포하기

파드 생성은 kubectl만 사용해도 가능하지만, 버전 관리 관점에서도 yaml 파일로 정의하는 것이 좋다. 쿠버네티스의 여러 가지 리소스를 정의하는 파일을 매니페스트 파일이라고 한다.

 

우선 매니페스트 파일을 이용하여 컨테이너 설정을 하기 이전에 간단히 도커 이미지와 컨테이너에 대해 다루어보자.

 

개념 역할
도커 이미지 도커 컨테이너를 구성하는 파일 시스템과 실행할 애플리케이션 설정을 하나로 합친 것으로 컨테이너를 생성하는 템플릿 역할을 한다.
도커 컨테이너 도커 이미지를 기반으로 생성되며, 파일 시스템과 애플리케이션이 구체화돼 실행되는 상태.

 

모든 소스 및 Dockerfile은 깃헙에 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@RestController
@SpringBootApplication
public class SampleApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SampleApplication.class, args);
    }
 
    @RequestMapping("/")
    public String index(){
        return "Hello Kubernetes!";
    }
 
}
cs

 

간단히 "localhost:8080/"을 호출하였을 때 "Hello Kubernetes!"를 출력하는 예제이다. 해당 애플리케이션을 도커이미지로 만들기 위한 Dockerfile이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM openjdk:8-jdk
# 어떤 이미지로부터 새로운 이미지를 생성할 지 지정. 플랫폼 : 버전 형태로 작성
MAINTAINER levi <1223yys@naver.com>
# Dockerfile을 생성-관리하는 사람
VOLUME /tmp
# 호스트의 directory를 docker 컨테이너에 연결. 즉 소스코드나 외부 설정파일을 커밋하지 않고 docker container에서 사용가능하도록 함
RUN mkdir -/app/
# 도커 이미지 생성시 실행
ADD ./build/libs/sample-0.0.1-SNAPSHOT.jar /app/app.jar
# 파일이나 디렉토리를 docker image로 복사
EXPOSE 8080
# 외부에 노출할 포트 지정
CMD ["java""-jar""/app/app.jar"]
# docker image가 실행될 때 기본으로 실행될 command
cs

 

해당 파일을 프로젝트 root에 위치시켜준다.(Dockerfile) 이후 해당 도커파일을 기반으로 이미지를 빌드해준다.

 

> docker build -t 1223yys/springboot-web:0.1.6 . #도커이미지 빌드
> docker image ls #이미지가 잘 생성되었는지 확인
> docker run -d -p 8080:8080 1223yys/springboot-web:0.1.6 #도커 컨테이너 실행
> docker ps #컨테이너가 잘 실행되고 있는지 확인

 

빌드 완료후 간단히 테스트를 위하여 로컬 도커에서 컨테이너를 실행해보자. 해당 이미지는 Docker Hub에 배포한 상태이다. 추후에 Kubernetes 리소스 파일 작성시 사용하기 위한 배포인 것이다.

 

> docker push 1223yys/springboot-web:0.1.6

 

파드(pod) 매니페스트 작성

 

apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    

 

kind는 이 파일에서 정의하는 쿠버네티스 리소스의 유형을 지정하는 속성이다. 이 파일은 파드를 정의하는 파일이므로 속성값이 Pod이다. kind 속성에 따라 spec 아래의 스키마가 변화한다. metadata는 이름 그대로 리소스에 부여되는 메타 데이터이다. spec은 리소스를 정의하기 위한 속성으로, 파드의 경우 파드를 구성하는 컨테이너를 containers 아래에 정의한다.

 

containers 속성 아래의 값들을 보자. name은 컨테이너 이름, image는 도커 허브에 저장된 이미지 태그값을 지정한다. 포트는 외부에 노출시킬 포트번호이다. 

 

이제 위의 설정을 바탕으로 팟을 띄워보자. 팟을 띄우기 전에 샘플용 네임스페이스 하나를 생성한다. 그리고 팟을 띄워보도록 한다.

 

> kubectl create namespace kube-sample
> kubectl apply -f pod-sample.yaml -n kube-sample
> kubectl get pod -n kube-sample

 

아래 이미지와 같이 나왔다면 팟이 정상적으로 뜬 것이다.

 

 

팟을 다루는 기타 명령이다.

 

pod-sample -> yaml 파일명
springboot-web -> pod 이름
kube-sample -> 네임스페이스 이름

> kubectl get pod #떠있는 팟 목록을 가져온다
> kubectl logs -f springboot-web -c springboot-web -n kube-sample #표준 출력 로그를 본다.
> kubectl delete pod springboot-web #pod 삭제
> kubectl delete -f pod-sample.yaml

 

여기서 파드에 대한 설명을 보충하자면, 파드에는 각각 고유의 가상 IP주소가 할당된다. 파드에 할당된 가상 IP 주소는 해당 파드에 속하는 모든 컨테이너가 공유한다. 즉, 같은 파드 안의 모든 컨테이너의 가상 IP 주소가 같기 때문에 컨테이너간의 localhost 통신이 가능해진다. 하지만 다른 팟 간에는 localhost 통신이 당연히 불가능하다.(할당된 가상 IP 주소가 다르기 때문)

 

레플리카세트(ReplicaSet)

파드를 정의한 매니페스트 파일로는 파드를 하나밖에 생성할 수 없다. 그러나 어느 정도 규모가 되는 애플리케이션을 구축하려면 같은 파드를 여러 개 실행해 가용성을 확보해야 하는 경우가 생긴다. 이런 경우 사용하는 것이 레플리카세트이다. 레플리카세트는 똑같은 정의를 갖는 파드 여러개를 생성하고 관리하기 위한 리소스이다.

 

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: sample-replicaset
  labels:
    app: springboot-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
      - name: web-app
        image: 1223yys/springboot-web:0.1.6
        ports:
        - containerPort: 8080

 

간단하게 매니페스트 파일을 설명하자면, apiVersion은 리소스 유형마다 조금씩 다르다. 왜냐 각 리소스마다 호출하는 api path가 다르기 대문에 잘 확인하자. 그리고 kind는 ReplicaSet이고, metadata에는 이름등을 정의한다. 두번째는 ReplicaSet의 spec 부분이다. 이전 Pod의 매니페스트와는 조금 다르게 replicas와 selector가 생겼다. 우선 replicas는 몇개의 복제본을 만들것인가를 정의하고, selector는 어떠한 Pod을 대상으로 ReplicaSet을 만들것인가를 지정한다. matchLabels의 값으로 template 밑의 라벨 값이 선택되었다. template 밑의 설정들은 Pod의 설정과 동일하다. 이 말은 즉, selector 속성으로 복제할 Pod의 설정을 참조하는 것이다. 이제 정말 레플리카셋이 잘 떴는지 확인하자.

 

> kubectl describe replicasets -n kube-sample

 

해당 명령은 레플리카셋의 리소스에 대한 자세한 설명이 담겨있다. 

 

> kubectl get replicasets,pod -n kube-sample

 

 

해당 명령으로 하나의 레플리카셋이 떠있으며 이 레플리카셋에 설정된 리소스로 Pod이 총 3개 떠있는 것을 확인할 수 있다.

 

디플로이먼트(deployment)

레플리카셋보다 상위에 해당하는 리소스로 디플로이먼트가 있다. 보통 디플로이먼트가 애플리케이션 배포의 기본 단위가 되는 리소스이다. 레플리카셋은 똑같은 팟의 레플리카를 관리 및 제어하는 리소스인데 반해, 디플로이먼트는 레플리카셋을 관리하고 다루기 위한 리소스이다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
  labels:
    app: springboot-web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
      - name: web-app
        image: 1223yys/springboot-web:0.1.6
        ports:
        - containerPort: 8080

 

사실 디플로이먼트의 설정은 레플리카셋과 크게 다르지 않다. 차이가 있다면 디플로이먼트가 레플리카셋의 리비전 관리를 할 수 있다는 정도이다.

 

 

총 리소스 타입이 3개가 뜬 것을 볼 수 있다.

 

 

디플로이먼트는 위와 같이 리비전이 관리된다. 하지만 change-cause가 보이지 않는다. 이것을 apply할때 옵션을 하나 빠트려서 그런다.

 

 

이렇게 디플로이먼트는 리비전관리가 되기 때문에 레플리카셋 대신 배포의 기본단위가 된다.

 

레플리카세트의 생애주기

 

쿠버네티스는 디플로이먼트를 단위로 애플리케이션을 배포한다. 실제 운영에서는 레플리카세트를 직접 다루기 보단 디플로이먼트 매니페스트 파일을 통해 다루는 경우가 대부분이다.

 

디플로이먼트가 관리하는 레플리카세트는 지정된 개수만큼 파드를 화곱하거나 파드를 새로운 버전으로 교체하거나 이전 버전으로 롤백하는 등 중요한 역할을 한다.

 

애플리케이션 배포를 바르게 운영하려면 이러한 레플리카세트가 어떻게 동작하는지 파악할 수 있어야 한다. 디플로이먼트를 수정하면 레플리카세트가 새로 생성되고 기존 레플리카세트와 교체된다.

 

파드 개수만 수정하면 레플리카세트가 새로 생성되지 않음

매니페스트 파일에서 replicas 값을 3->4로 수정해 반영하자. 그리고 팟의 정보를 보면(kubectl get pod -n kube-sample) 기존 팟을 그대로 있고, 새로운 컨테이너가 새로 생성되는 것을 알 수 있다.

 

레플리카세트가 새로 생성됬다면 리비전 번호가 2일 텐데, 그 내용은 출력되지 않는다. replicas 값만 변경해서는 레플리카세트의 교체가 일어나지 않는다.

 

 

컨테이너 정의 수정

만약 새로운 API가 추가되어 컨테이너의 수정이 있다고 해보자. 그렇다면 레플리카셋은 어떻게 될까? 새로운 엔드포인트를 추가하여 도커이미지를 새로운 태그로 빌드하고 디플로이먼트의 Pod의 이미지 태그를 변경해보자.

 

 

기존 팟들이 내려가고 새로운 팟들이 생성되고 있으며, 새로운 리비전이 하나 생겨났다.

 

롤백 실행하기

디플로이먼트는 리비전 번호가 기록되므로 특정 리비전의 내용을 확인할 수 있다.

 

> kubectl rollout history deployment sample-deployment --revision=1

 

 

> kubectl rollout undo deployment sample-deployment -n kube-sample

 

위 undo 명령을 실행하면 바로 이전의 리비전 상태의 디플로이먼트로 되돌아 갈 수 있다. 만약 애플리케이션 배포후 문제가 있다면 바로 롤백할 수 있기때문에 좋은 기능이 될 것이다.

 

여기까지 쿠버네티스가 무엇인지 Pod,RepliaSets,Deployment에 대해 간단히 다루어보았다. 하지만 여기까지 진행해서는 실제 애플리케이션을 사용해 볼수 없다. 우리가 띄운 Pod을 외부에 노출시키기 위해서는 서비스, 인그레스라는 애들이 필요하다. 서비스 및 인그레스 리소스는 다음 포스팅에서 다루어볼 것이다.

posted by 여성게
: