인프라/Docker&Kubernetes 2020. 8. 30. 17:02

오늘 다루어볼 내용은 kustomize이다. Kustomize는 kustomization 파일을 이용해 kubernetes 오브젝트를 사용자가 원하는 대로 변경(customize)하는 도구이다.

 

모든 예제는 아래 깃헙 kube-kustomize 디렉토리에 있다.

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

kustomization 파일을 포함하는 디렉터리 내의 리소스를 보거나 실제 클러스터에 리소스를 적용하려면 다음 명령어를 이용한다.

 

#kustomize가 적용된 설정파일 결과를 보여준다. 
> kubectl kustomize <kustomization_directory> 
#실제 kustomize 리소스를 클러스터에 적용한다. 
> kubectl apply -k <kustomization_directory>

 

Kustomize

Kustomize는 쿠버네티스 구성을 사용자 정의화하는 도구이다. 이는 애플리케이션 구성 파일을 관리하기 위해 다음 기능들을 가진다.

  • 다른 소스에서 리소스 생성
  • 리소스에 대한 교차 편집 필드 설정
  • 리소스 집합을 구성하고 사용자 정의

 

교차 편집 필드 설정

프로젝트 내 모든 쿠버네티스 리소스에 교차 편집 필드를 설정하는 것은 꽤나 일반적이다. 교차 편집 필드를 설정하는 몇 가지 사용 사례는 다음과 같다.

  • 모든 리소스에 동일한 네임스페이스를 설정
  • 동일한 네임 접두사 또는 접미사를 추가
  • 동일한 레이블들을 추가
  • 동일한 어노테이션들을 추가

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

# deployment.yaml을 생성
cat <<EOF >./deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
EOF

cat <<EOF >./kustomization.yaml
namespace: my-namespace
namePrefix: dev-
nameSuffix: "-001"
commonLabels:
  app: bingo
commonAnnotations:
  oncallPager: 800-555-1212
resources:
- deployment.yaml
EOF
> kubectl kustomize ./kube-kustomize/kustomize-upsert-field

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    oncallPager: 800-555-1212
  labels:
    app: bingo
  name: dev-nginx-deployment-001
  namespace: my-namespace
spec:
  selector:
    matchLabels:
      app: bingo
  template:
    metadata:
      annotations:
        oncallPager: 800-555-1212
      labels:
        app: bingo
    spec:
      containers:
      - image: nginx
        name: nginx

 

구성(composition)

한 파일에 deployment, service 등을 정의하는 것은 일반적이다. kustomize는 서로 다른 리소스들을 하나의 파일로 구성할 수 있게 지원한다.

 

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

# deployment.yaml 파일 생성
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# service.yaml 파일 생성
cat <<EOF > service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF

# 이들을 구성하는 kustomization.yaml 생성
cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF
> kubectl kustomize /kube-kustomize/kustomize-composition

apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80

 

 

사용자 정의(user patch define)

패치는 리소스에 다른 사용자 정의를 적용하는 데 사용할 수 있다. Kustomize는 patchesStrategicMerge와 patchesJson6902를 통해 서로 다른 패치 메커니즘을 지원한다. patchesStrategicMerge는 파일 경로들의 리스트이다. 각각의 파일은 patchesStrategicMerge로 분석될 수 있어야 한다. 패치 내부의 네임은 반드시 이미 읽혀진 리소스 네임(ex. deployment.yaml 안의 이름)과 일치해야 한다. 한 가지 일을 하는 작은 패치가 권장된다. 예를 들기 위해 디플로이먼트 레플리카 숫자를 증가시키는 하나의 패치와 메모리 상한을 설정하는 다른 패치를 생성한다.

 

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

# deployment.yaml 파일 생성
cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

# increase_replicas.yaml 패치 생성
cat <<EOF > increase_replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
EOF

# 다른 패치로 set_memory.yaml 생성
cat <<EOF > set_memory.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  template:
    spec:
      containers:
      - name: my-nginx
        resources:
        limits:
          memory: 512Mi
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
patchesStrategicMerge:
- increase_replicas.yaml
- set_memory.yaml
EOF
> kubectl kustomize /kube-kustomize/kustomize-patchesStrategicMerge

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi

 

모든 리소스 또는 필드가 patchesStrategicMerge를 지원하는 것은 아니다. 임의의 리소스 내 임의의 필드의 수정을 지원하기 위해, Kustomize는 patchesJson6902를 통한 JSON 패치 적용을 제공한다. Json 패치의 정확한 리소스를 찾기 위해, 해당 리소스의 group, version, kind, name이 kustomization.yaml 내에 명시될 필요가 있다. 예를 들면, patchesJson6902를 통해 디플로이먼트의 리소스만 증가시킬 수 있다. 또한 patchesStrategicMerge, patchesJson6902를 같이 혼합해서 사용도 가능하다.

 

#deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - name: my-nginx
          image: nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 256Mi

#patch-replica.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3

#patch-resource.yaml
- op: replace
  path: /spec/template/spec/containers/0/resources/limits/memory
  value: 512Mi

#kustomization.yaml
resources:
  - deployment.yaml

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml
> kubectl kustomize /kube-kustomize/kustomize-patchesJson6902

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: nginx
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi

 

patchesJson6902는 "replace"라는 오퍼레이션 말고, add, remove, move, copy, test라는 오퍼레이션도 존재한다.

patch images

patch 파일을 생성하지 않고, 컨테이너의 이미지를 재정의 할 수 있다.

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
        ports:
        - containerPort: 80
EOF

cat <<EOF >./kustomization.yaml
resources:
- deployment.yaml
images:
- name: nginx
  newName: my.image.registry/nginx
  newTag: 1.4.0
EOF
> kubectl kustomize /kube-kustomize/kustomize-patch-images

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80

 

Base&Overlay

Kustomize는 base와 overlay의 개념을 가지고 있다. base는 kustomization.yaml과 함께 사용되는 디렉터리다. 이는 사용자 정의와 관련된 리소스들의 집합을 포함한다. kustomization.yaml의 내부에 표시되는 base는 로컬 디렉터리이거나 원격 리포지터리의 디렉터리가 될 수 있다. overlay는 kustomization.yaml이 있는 디렉터리로 다른 kustomization 디렉터리들을 bases로 참조한다. base는 overlay에 대해서 알지 못하며 여러 overlay들에서 사용될 수 있다. 한 overlay는 다수의 base들을 가질 수 있고, base들에서 모든 리소스를 구성할 수 있으며, 이들의 위에 사용자 정의도 가질 수 있다.

 

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

# base를 가지는 디렉터리 생성
mkdir base
# base/deployment.yaml 생성
cat <<EOF > base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
spec:
  selector:
    matchLabels:
      run: my-nginx
  replicas: 2
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
      - name: my-nginx
        image: nginx
EOF

# base/service.yaml 파일 생성
cat <<EOF > base/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: my-nginx
  labels:
    run: my-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
  selector:
    run: my-nginx
EOF
# base/kustomization.yaml 생성
cat <<EOF > base/kustomization.yaml
resources:
- deployment.yaml
- service.yaml
EOF

 

이 base는 다수의 overlay에서 사용될 수 있다. 다른 namePrefix 또는 다른 교차 편집 필드들을 서로 다른 overlay에 추가할 수 있다. 다음 예제는 동일한 base를 사용하는 두 overlay들이다.

 

> mkdir dev

cat <<EOF > dev/kustomization.yaml
#구버전 base 불러오는 방법
bases:
  - ../base
#resources:
#- ../base/kustomization.yaml

namespace: dev-my-nginx

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml

images:
  - name: nginx
    newName: my.image.registry/nginx
    newTag: 1.4.0
EOF

mkdir prod
cat <<EOF > prod/kustomization.yaml
#구버전 base 불러오는 방법
bases:
  - ../base
#resources:
#- ../base/kustomization.yaml

namespace: prod-my-nginx

patchesStrategicMerge:
  - patch-replica.yaml

patchesJson6902:
  - target:
      kind: Deployment
      name: my-nginx
      group: apps
      version: v1
    path: patch-resource.yaml

images:
  - name: nginx
    newName: my.image.registry/nginx
    newTag: 1.4.0
EOF

 

추가적으로 patch 파일들을 몇가지 작성하였다.

 

> cd dev
> kubectl kustomize ./

#dev
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: dev-my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: dev-my-nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 512Mi

> cd ../prod
> kubectl kustomize ./

#prod
apiVersion: v1
kind: Service
metadata:
  labels:
    run: my-nginx
  name: my-nginx
  namespace: prod-my-nginx
spec:
  ports:
    - port: 80
      protocol: TCP
  selector:
    run: my-nginx
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-nginx
  namespace: prod-my-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      run: my-nginx
  template:
    metadata:
      labels:
        run: my-nginx
    spec:
      containers:
        - image: my.image.registry/nginx:1.4.0
          name: my-nginx
          ports:
            - containerPort: 80
          resources:
            limits:
              memory: 1024Mi

 

여기까지 쿠버네티스 설정 파일들을 관리하기 위한 방법으로 kustomize에 대해 간단히 다루어보었다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 8. 24. 22:35

이번 포스팅에서는 쿠버네티스 로깅 파이프라인 구성에 대해 다루어볼 것이다. 저번 포스팅에서는 Fluentd + ES + Kibana 조합으로 클러스터 로깅 시스템을 구성했었는데, 이번 시간에는 Fluentd + kafka + ELK 조합으로 구성해본다.

<fluentd + ES + kibana logging>

 

 

Kubernetes - Kubernetes 로깅 운영(logging), Fluentd

오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실

coding-start.tistory.com

중간에 카프카를 두는 이유는 여러가지가 있을 수 있을 것 같다. 첫번째 버퍼역할을 하기때문에 어느정도 파이프라인의 속도 조절이 가능하다. 두번째 로그를 카프카 큐에 담아두고, 여러 컨슈머 그룹이 각기의 목적으로 로그데이터를 사용가능하다. 바로 실습에 들어가보자.

 

구성

 

 

구성은 위 그림과 같다. fluentd는 컨테이너 로그를 tail하고 있고, tail한 데이터를 카프카로 프로듀싱한다. 그리고 아웃풋으로 로그스태시로 보내고 로그 스태시는 엘라스틱서치에 색인을하게 된다.

 

실습이전에 본 실습에서 진행하는 예제중 카프카 구성과 엘라스틱서치의 구성은 별도로 옵션 튜닝 및 물리머신에 구성하는 것이 좋다. 필자는 구성의 편의를 위해 아무런 옵션을 튜닝하지 않은채 같은 쿠버네티스 클러스터에 카프카와 엘라스틱서치를 구성하였다.

 

kafka install & deploy on kubernetes unsing helm
 

TheOpenCloudEngine/uEngine-cloud-k8s

Contribute to TheOpenCloudEngine/uEngine-cloud-k8s development by creating an account on GitHub.

github.com

<헬름 설치>

> curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
> kubectl --namespace kube-system create sa tiller
> kubectl create clusterrolebinding tiller --clusterrole cluster-admin --serviceaccount=kube-system:tiller
> helm init --service-account tiller
> helm repo update

위 명령어로 헬름을 다운로드 받는다.

 

<카프카 헬름 차트 설치 및 배포>

> kubectl create ns kafka
> helm repo add incubator http://storage.googleapis.com/kubernetes-charts-incubator
> helm install --name my-kafka --namespace kafka incubator/kafka

 

kafka라는 별도의 네임스페이스를 생성하여 그 안에 카프카를 배포하였다.

 

<헬름차트 삭제>

차트 삭제가 필요하면 아래 명령어를 이용하자.

# --purge 옵션으로 관련된 모든 정보를 지운다. 
helm delete my-kafka --purge

 

<fluentd가 데이터를 보낼 토픽생성>

> kubectl -n kafka exec my-kafka-0 -- /usr/bin/kafka-topics \
--zookeeper my-kafka-zookeeper:2181 --topic fluentd-container-logging \
--create --partitions 3 --replication-factor 3

Created topic "fluentd-container-logging".

 

"fluentd-container-logging"이라는 이름으로 토픽을 생성하였다.

 

<생성된 topic 확인>

> kubectl -n kafka exec my-kafka-0 -- /usr/bin/kafka-topics --zookeeper my-kafka-zookeeper:2181 --list

fluentd-container-logging

 

토픽리스트를 조회해서 우리가 생성한 토픽이 있는지 조회해본다.

 

<fluentd가 보낸 데이터가 큐로 잘들어오는지 확인하기 위해 컨슘머 실행>

> kubectl -n kafka exec -ti my-kafka-0 -- /usr/bin/kafka-console-consumer \
--bootstrap-server my-kafka:9092 --topic fluentd-container-logging --from-beginning

 

이제 실제로 카프카와 주키퍼가 쿠버네티스에 잘 떠있는지 확인해보자 !

 

> kubectl get pod,svc -n kafka
  NAME                       READY   STATUS    RESTARTS   AGE
  pod/my-kafka-0             1/1     Running   2          4m14s
  pod/my-kafka-1             1/1     Running   0          116s
  pod/my-kafka-2             1/1     Running   0          78s
  pod/my-kafka-zookeeper-0   1/1     Running   0          4m14s
  pod/my-kafka-zookeeper-1   1/1     Running   0          3m32s
  pod/my-kafka-zookeeper-2   1/1     Running   0          3m
  NAME                                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
  service/my-kafka                      ClusterIP   10.108.104.66   <none>        9092/TCP                     4m14s
  service/my-kafka-headless             ClusterIP   None            <none>        9092/TCP                     4m14s
  service/my-kafka-zookeeper            ClusterIP   10.97.205.63    <none>        2181/TCP                     4m14s
  service/my-kafka-zookeeper-headless   ClusterIP   None            <none>        2181/TCP,3888/TCP,2888/TCP   4m14s

 

위와 같이 팟과 서비스 목록이 보인다면 다음으로 넘어간다.

 

ELK Stack 구성

<elasticsearch 실행>

아래 deployment와 service 설정파일을 이용하여 쿠버네티스 위에 엘라스틱서치를 구성한다.

 

apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: elk-stack
spec:
  selector:
    app: elasticsearch
  ports:
    - port: 9200
      protocol: TCP
      targetPort: 9200
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: elk-stack
  labels:
    app: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elastic/elasticsearch:6.8.6
        ports:
        - containerPort: 9200
          name: http
        - containerPort: 9300
          name: tcp

 

위 설정 파일은 볼륨을 구성하지 않아서 일회성(테스트)로만 가능하다. 실제로 운영환경에서는 물리머신에 클러스터를 구성하던가, 혹은 쿠버네티스 볼륨을 붙여서 구성하자.

 

> kubectl apply -f ./kube-logging/fluentd-elasticsearch/elasticsearch.yaml
> kubectl get pod,svc -n elk-stack
  NAME                                 READY   STATUS    RESTARTS   AGE
  pod/elasticsearch-654c5b6b77-l8k2z   1/1     Running   0          50s
  NAME                    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
  service/elasticsearch   ClusterIP   10.101.27.73   <none>        9200/TCP   50s

 

<kibana 실행>

키바나는 아래 설정파일을 예제로 구성하였다.

 

apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: elk-stack
spec:
  selector:
    app: kibana
  ports:
  - protocol: TCP
    port: 5601
    targetPort: 5601
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: elk-stack
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: elastic/kibana:6.8.6
        ports:
        - containerPort: 5601
          name: http

 

위 설정중 조금 살펴봐야할 것은 서비스 타입을 NodePort로 준 점이다. 실제로 외부로 포트를 개방해 localhost로 접근 가능하다. 실제 운영환경에서는 ingress까지 구성하여 배포하자.

 

> kubectl apply -f ./kube-logging/fluentd-elasticsearch/kibana.yaml
> kubectl get pod,svc -n elk-stack | grep kibana
  NAME                                 READY   STATUS    RESTARTS   AGE
  pod/kibana-6d474df8c6-fsfc7          1/1     Running   0          24s
  NAME                                 READY   STATUS    RESTARTS   AGE
  service/kibana          NodePort    10.97.240.55   <none>        5601:30578/TCP   24s

 

http://localhost:30578로 접근해 키바나가 잘 떠있는지와 엘라스틱서치와 잘 연동되었는지 확인하자.

 

<logstash 실행>

로그스태시는 아래 예시 설정 파일로 구성하였다.

 

apiVersion: v1
kind: ConfigMap
metadata:
  name: logstash-configmap
  namespace: elk-stack
data:
  logstash.yml: |
    http.host: "127.0.0.1"
    path.config: /usr/share/logstash/pipeline
    pipeline.workers: 2
  logstash.conf: |
    # all input will come from filebeat, no local logs
    input {
      kafka {
        bootstrap_servers => "my-kafka.kafka.svc.cluster.local:9092"
        topics => "fluentd-container-logging"
        group_id => "fluentd-consumer-group"
        enable_auto_commit => "true"
        auto_offset_reset => "latest"
        consumer_threads => 4
        codec => "json"
      }
    }

    output {
        elasticsearch {
          hosts => ["http://elasticsearch.elk-stack.svc.cluster.local:9200"]
          manage_template => false
          index => "kubernetes-container-log-%{+YYYY-MM-dd}"
        }
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: logstash-deployment
  namespace: elk-stack
spec:
  replicas: 1
  selector:
    matchLabels:
      app: logstash
  template:
    metadata:
      labels:
        app: logstash
    spec:
      containers:
        - name: logstash
          image: docker.elastic.co/logstash/logstash:5.6.0
          ports:
            - containerPort: 5044
          volumeMounts:
            - name: config-volume
              mountPath: /usr/share/logstash/config
            - name: logstash-pipeline-volume
              mountPath: /usr/share/logstash/pipeline
      volumes:
        - name: config-volume
          configMap:
            name: logstash-configmap
            items:
              - key: logstash.yml
                path: logstash.yml
        - name: logstash-pipeline-volume
          configMap:
            name: logstash-configmap
            items:
              - key: logstash.conf
                path: logstash.conf
---
apiVersion: v1
kind: Service
metadata:
  name: logstash-service
  namespace: elk-stack
spec:
  selector:
    app: logstash
  ports:
    - protocol: TCP
      port: 5044
      targetPort: 5044
  type: ClusterIP

 

설정에서 잘 살펴볼 것은 input과 output의 호스트 설정이다. 우리는 모든 모듈을 같은 클러스터에 설치할 것이기 때문에 쿠버네티스 내부 DNS를 사용하였다.(실습에 편의를 위한 것이기도 하지만, 실제 운영환경에서도 내부 시스템은 종종 클러스터 내부 DNS를 사용하기도 한다. 그러면 실제로 통신하기 위해 클러스터 밖으로 나갔다 오지 않는다.)

 

또 한가지 설정은 Deployment에 볼륨을 마운트 하는 부분이다. 실제 쿠버네티스에서 ConfigMap은 볼륨으로 잡히기 때문에 그 ConfigMap을 logstash pod 내부로 마운트하여 실행시점에 해당 설정파일을 물고 올라가도록 하였다.

 

> kubectl apply -f ./kube-logging/fluentd-elasticsearch/logstash.yaml
> kubectl get pod,svc -n elk-stack | grep logstash
  NAME                                       READY   STATUS    RESTARTS   AGE  
  pod/logstash-deployment-556cfb66b5-6xrs6   1/1     Running   0          34s
  service/logstash-service   ClusterIP   10.96.13.170   <none>        5044/TCP         33s

 

<fluentd 실행>

이제는 실제 컨테이너 로그를 tail하여 수집하는 fluentd를 실행시켜보자.

 

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      app: fluentd-logging
  template:
    metadata:
      labels:
        app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      tolerations:
        - key: node-role.kubernetes.io/master
          effect: NoSchedule
      containers:
        - name: fluentd
          image: 1223yys/fluentd-kafka:latest
          imagePullPolicy: Always
          env:
            - name: FLUENT_KAFKA_BROKERS
              value: "my-kafka.kafka.svc.cluster.local:9092"
            - name: FLUENT_KAFKA_DEFAULT_TOPIC
              value: "fluentd-container-logging"
            - name: FLUENT_KAFKA_OUTPUT_DATA_TYPE
              value: "json"
            - name: FLUENT_KAFKA_COMPRESSION_CODEC
              value: "snappy"
            - name: FLUENT_KAFKA_MAX_SEND_LIMIT_BYTES
              value: "4096"
          resources:
            limits:
              memory: 200Mi
            requests:
              cpu: 100m
              memory: 200Mi
          volumeMounts:
            - name: varlog
              mountPath: /var/log
            - name: varlibdockercontainers
              mountPath: /var/lib/docker/containers
              readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
        - name: varlog
          hostPath:
            path: /var/log
        - name: varlibdockercontainers
          hostPath:
            path: /var/lib/docker/containers

 

fluentd 설정파일은 몇가지 짚고 넘어갈 것들이 있다. 첫번째는 컨테이너를 tail하기 위해 마운트한 설정이다. /var/log, /var/lib/docker/container를 마운트하였다. 실제 호스트머신에 해당 디렉토리에 들어가면 파일이 보이지 않을 것이다. 만약 파일을 보고 싶다면 아래 설정을 통해 도커 컨테이너를 실행시키고 볼 수 있다.

 

> docker run -it --rm -v /var/lib/docker/containers:/json-log alpine ash

 

위 도커이미지를 실행한후 /json-log 디렉토리에 들어가면 호스트머신에 쌓인 컨테이너 로그들을 볼 수 있다.

 

두번째, tail한 로그를 내보내기 위한 env 설정이다. 아웃풋은 카프카로 두었고, 역시 도메인은 내부 클러스터 DNS로 잡아주었다. 그리고, 우리가 미리 생성한 토픽에 데이터를 보내고 있고 타입은 json으로 보내고 있다.(사실상 튜닝할 설정은 많지만 실습의 편의를 위해 대부분 기본 설정으로 잡았다.)

 

그리고 필자가 fluentd 이미지를 새로 빌드한 이유는 카프카로 보내는 로그 포맷을 수정하기 위하여 fluentd 설정파일들을 조금 수정하였기 때문이다. 혹시나 fluentd 설정 파일들이 궁금하다면 포스팅 마지막 Github을 참조하자.(https://github.com/yoonyeoseong/kubernetes-sample/tree/master/kube-logging/fluentd-kafka)

 

> kubectl apply -f ./kube-logging/fluentd-kafka/fluentd-kafka-daemonset.yaml
> kubectl get pod,daemonset -n kube-system | grep fluentd
  NAME                                         READY   STATUS    RESTARTS   AGE
  pod/fluentd-bqmnl                            1/1     Running   0          34s
  daemonset.extensions/fluentd      1         1         1       1            1           <none>                        34s

 

이제 로그 출력을 위해 샘플 앱을 실행시켜보자. 로그 출력을 위한 앱은 꼭 아래 필자가 빌드한 웹 어플리케이션을 실행시킬 필요는 없다. 만약 아래 애플리케이션을 실행시키려면 ingress 설정 혹은 service node port를 설정하자.

 

> kubectl apply -f ./kube-resource/deployment-sample.yaml
> kubectl get pod
  NAME                                 READY   STATUS    RESTARTS   AGE
  sample-deployment-5fbf569554-4pzrf   0/1     Running   0          17s

 

이제 요청을 보내보자.

 

> kubectl get svc -n ingress-nginx
  NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
  ingress-nginx-controller             NodePort    10.97.27.106   <none>        80:30431/TCP,443:31327/TCP   21d
  ingress-nginx-controller-admission   ClusterIP   10.96.76.113   <none>        443/TCP                      21d
> curl localhost:30431/api

 

이제 키바나에 접속해보면 앱에서 출력하고 있는 로그 데이터를 볼 수 있다. 모든 예제 설정 및 코드는 아래 깃헙을 참고하자 !

 

 

yoonyeoseong/kubernetes-sample

Kubernetes(쿠버네티스) sample. Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 8. 15. 22:31

 

오늘은 쿠버네티스의 볼륨에 대해 다루어 볼 것이다. 간단하게 몇가지 볼륨 플러그인에 대해 예제를 다루어보고, 퍼시스턴트 볼륨&볼륨 클레임에 대해 다루어본다.

 

 

yoonyeoseong/kubernetes-sample

Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

 

emptyDir

해당 플러그인은 pod가 실행되는 호스트의 디스크를 임시로 컨테이너에 볼륨으로 할당해서 사용하는 방법이다. pod가 사라지면 emptyDir에 마운트해서 사용하는 데이터도 모두 사라진다. 하지만, pod이 종료되지 않고 단순히 container만 재시작된 것이라면 데이터는 유지된다. 주로 대용량 데이터 계산의 중간 연산 저장 용도로 사용한다.

 

apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /emptyDir
        name: emptyDir_vol
  volumes:
    - name: emptyDir_vol
      emptyDir: {}

 

hostPath

pod가 실행된 호스트의 파일이나 디렉터리를 pod에 마운트한다. emptyDir가 임시 디렉터리를 마운트하는 것이라면, hostPath는 호스트에 있는 실제 파일이나 디렉터리를 마운트하는 것이며 pod를 재시작하더라도 데이터가 보존된다. 보통은 /var/lib/docker 같은 도커 시스템용 디렉토리를 컨테이너에 마운트해 시스템 모니터링 등을 진행할 때 사용하기도 한다.

 

apiVersion: v1
kind: Pod
metadata:
  name: springboot-web
spec:
  containers:
  - name: springboot-web
    image: 1223yys/springboot-web:0.1.6
    ports:
    - containerPort: 8080
    volumeMounts:
      - mountPath: /test-volume
        name: hostPath-vol
  volumes:
    - name: hostPath-vol
      hostPath:
        path: /tmp
        type: Directory

 

실제로 볼륨이 잘 마운트 되었는지 확인해보자.

 

> kubectl exec <pod-name> -it sh
> cd /test-volume
> touch test.txt
> exit
> ls /tmp

 

호스트의 /tmp 디렉토리에 test.txt가 생성되었다면, 볼륨이 잘 마운트 된 것이다. hostPath 볼륨에는 아래와 같이 여러가지 타입이 존재한다.

 

  • null: hostPath 볼륨을 마운트하기 전에 아무것도 확인하지 않는다.
  • DirectoryOrCreate: 설정한 경로에 디렉터리가 없으면 퍼미션이 755인 빈 디렉터리를 만든다.
  • Directory: 설정한 경로에 디렉터리가 존재해야한다. 호스트에 해당 디렉터리가 없으면 파드는 ContainerCreating 상태로 남고 생성이 안된다.
  • FileOrCreate: 설정한 경로에 파일이 없으면 퍼미션이 644인 빈 파일을 만든다.
  • File: Directory와 동일
  • Socket: 설정한 경로에 유닉스 소켓 파일이 있어야한다.

 

퍼시스턴트 볼륨&볼륨 클레임(Persistent Volume, Persistent Volume Claim)

쿠버네티스에서 볼륨을 사용하는 구조는 PV와 PVC로 분리되어있다. PV는 볼륨 자체를 뜻하고 클러스터 안에서 자원으로 다룬다. 파드하고는 별개로 관리되고 별도의 생명주기가 있다.

 

 

PVC는 사용자가 PV에 하는 요청이다. 사용하고 싶은 용량은 얼마인지, 읽기/쓰기는 어떤 모드를 사용하고 싶은지 등을 정하여 요청한다. 즉, 쿠버네티스는 이처럼 파드에 볼륨을 직접 할당하는 형태가 아니라, 중간에 PVC를 두어 파드와 파드가 사용할 스토리지를 분리하는 전략인 것이다. 이렇게 분리됨으로써 이점은 다양한 스토리지를 PV로 사용할 수 있는데, 파드는 어떠한 스토리지의 볼륨인지 신경쓸 필요없이 PVC으로 요청만 하면 되기 때문에 의존성이 줄어들게 되고, manifest도 분리됨으로써 설정파일 자체의 복잡함이 사라진다.

 

PV&PVC 생명주기
  • Provisioning: PV를 만드는 단계를 뜻한다.
    • static provisioning: 미리 PV를 만들어 두고 사용자의 요청이 있으면 미리 만들어둔 PV를 할당한다.(보통 스토리지 용량의 제한이 있을때 사용한다.)
    • dynamic provisioning: 사용자가 PVC를 거쳐 PV를 요청했을 때, PV를 생성해 제공한다.
  • Binding: 바인딩은 프로비저닝으로 만든 PV를 PVC와 연결하는 단계이다. PVC에서 원하는 스토리지의 용량과 접근 방법을 명시해서 요쳥하면 맞는 PV가 할당된다. 이때 PVC에서 원하는 PV가 없다면 요청은 실패하고, PVC에서 원하는 PV가 생길 때까지 대기하다가 PVC에 바인딩된다.(PVC 하나에 여러 PV가 매핑될 수 없다.)
  • Using: PVC는 파드에 설정되고 파드는 PVC를 볼륨으로 인식해서 사용한다. 할당된 PVC는 파드를 유지하는 동안 계속 사용하며 시스템에서 임의로 삭제할 수 없다. 이 기능을 "Storage Object In Use Protection"이라 한다.
  • Reclaiming: 사용이 끝난 PVC는 삭제되고 PVC를 사용하던 PV를 초기화하는 과정을 뜻한다. 초기화 정책으로는 아래와 같다.
    • Retain: PV를 그대로 보존한다. PVC가 삭제되면 사용 중이던 PV는 해제(released)상태라서 아직 다른 PVC가 재사용할 수 없다.(데이터는 아직 그대로 보존되어있다.) 만약 해당 PV를 재사용하려면 아래와 같은 순서로 직접 초기화해줘야한다.
      1. PV삭제. 만약 PV가 외부 스토리지와 연결되어있다면 PV는 삭제되더라도 외부 스토리지의 볼륨은 그대로 남아있다.
      2. 외부 스토리지에 남은 데이터를 직접 정리한다.
      3. 남은 스토리지의 볼륨을 삭제하거나 재사용하려면 해당 볼륨을 이용하는 PV를 다시 만든다.
    • Delete: PV를 삭제하고 연결된 외부 스토리지 쪽의 볼륨도 삭제한다. 동적 프로비저닝은 기본적으로 해당 정책을 따른다.
    • Recycle: PV의 데이터들을 삭제하고 다시 새로운 PVC에서 PV를 사용할 수 있도록한다.

 

이제 실제로 실습을 통해 알아보자. 다음은 퍼시스턴트 볼륨 템플릿이다.

 

apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp

 

간단하게 설정파일에 작성된 설정을 설명하면 아래와 같다.

 

  • accessModes
    • ReadWriteOne: 노드 하나에만 볼륨을 읽기/쓰기하도록 마운트한다.
    • ReadOnlyMany: 여러 개 노드의 읽기 전용으로 마운트한다.
    • ReadWriteMany: 여러 개 노드에서 읽기/쓰기를 허용하도록 마운트한다.
  • storageClassName: 스토리지 클래스를 설정하는 필드이고, PVC가 특정 스토리지 클래스를 명시하여 요청하면 해당 스토리지 클래스로 선언된 PV와 연결된다. 만약 스토리지 클래스를 설정하지 않았다면, 특정 스토리지 클래스를 명시하지 않은 PVC가 요청하면 매핑된다.
  • persistentVolumeReclaimPolicy: PV가 해제되었을 때의 초기화 옵션을 설정한다.(Retain/Recycle/Delete)
  • .spec.hostPath: 해당 PV의 볼륨 플러그인을 명시한다.

 

볼륨이 잘 생성되었는지 확인해보자.

 

> kubectl apply -f ./kube-resource/persistent-volume-sample.yaml
> kubectl get pvc -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Available           manual       

 

STATUS는 아래와 같이 4가지의 상태값을 갖는다.

 

  • Available: PVC에서 사용할 수 있는 상태
  • Bound: 특정 PVC에 연결된 상태
  • Released: PVC는 삭제되었고, PV는 아직 초기화되지 않은 상태
  • Failed: 자동 초기화를 실패한 상태

 

다음은 퍼시스턴트 볼륨 클레임 설정이다.

 

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi

 

다른 내용은 볼륨의 설정과 크게 차이가 없고 한가지만 설명하자면, ".spec.resources.requests.storage"는 자원을 얼마나 사용할 것인지 명시하는 것이며, PV의 용량보다 높다면, 할당되지 않고 Pending 상태가 된다.

 

볼륨 클레임이 잘 생성되었는지 확인해보자.

 

> kubectl apply -f ./kube-resource/persistent-volume-claim-sample.yaml
> kubectl get pvc -n levi-volume-claim
NAME                      STATUS   VOLUME              CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistent-volume-claim   Bound    persistent-volume   1Gi        RWO            manual         11s
> kubectl get pv -n levi-volume
NAME                CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                       STORAGECLASS   REASON   AGE
persistent-volume   1Gi        RWO            Delete           Bound    levi-volume-claim/persistent-volume-claim   manual                  4h44m

 

PVC와 PV가 binding 된 이후에는 각각 STATUS가 Bound 상태로 변경되었다. 보통 PV와 PVC를 연동할때는 storageClassName을 보고 연결되는데, 또 다른 방법으로는 label로 연결할 수도 있다.

 

#볼륨
apiVersion: v1
kind: PersistentVolume
metadata:
  name: persistent-volume
  namespace: levi-volume
  labels:
    location: local
spec:
  capacity:
    storage: 1Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  persistentVolumeReclaimPolicy: Delete
  hostPath:
    path: /tmp

#볼륨 클레임
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: persistent-volume-claim
  namespace: levi-volume-claim
spec:
  volumeMode: Filesystem
  accessModes:
    - ReadWriteOnce
  storageClassName: manual
  resources:
    requests:
      storage: 500Mi
  selector:
    matchLabels:
      location: local

 

마지막으로 pod에 볼륨을 마운트해보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 2
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          volumeMounts:
            - mountPath: /test-volume
              name: persistent-volume
          livenessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 9090
              path: /api
            initialDelaySeconds: 60
      volumes:
        - name: persistent-volume
          persistentVolumeClaim:
            claimName: persistent-volume-claim

 

만약 예제대로 따라왔다면, pod은 뜨지 못하고 pending된 상태로 머물러 있을 것이다. 왜냐하면 클러스터는 클레임을 사용하는 pod와 동일한 네임스페이스에 있어야하기 때문이다. 위 deployment는 네임스페이스가 default이므로, 볼륨 클레임을 default 네임스페이스에 하나 생성해주어야 한다.

 

여기까지 정말 간단하게 쿠버네티스 볼륨과 볼륨 클레임에 대해 다루어보았다. 사실 다루어볼 볼륨 플러그인이 아주 많기 때문에 다음 포스팅에서 더 자세히 다루어볼 것이다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 8. 2. 22:52

 

이번 포스팅에서 다루어볼 내용은 간단하게 쿠버네티스 ingress-nginx를 설치하고, 외부 트래픽을 내부 팟에게 전달해주는 예제이다. 바로 예제로 들어간다.

 

> git clone https://github.com/kubernetes/ingress-nginx.git
> cd ./ingress-nginx/deploy/static/provider/baremetal
> kubectl apply -f .
> kubectl get deploy -n ingress-nginx
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
ingress-nginx-controller   1/1     1            1           60s

 

여기까지 따라왔다면 설치는 완료되었고, ingress-nginx를 위한 서비스 등이 떴을 것이다.

 

kubectl get svc -n ingress-nginx
NAME                                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller             NodePort    10.97.27.106   <none>        80:30431/TCP,443:31327/TCP   4m35s

 

30431로 접속해보자.

 

> curl levi.local.com:30431
<html>
<head><title>404 Not Found</title></head>
<body>
<center><h1>404 Not Found</h1></center>
<hr><center>nginx/1.19.1</center>
</body>
</html>

 

호스트 설정을 통해 levi.local.com을 localhost로 포워딩하도록 설정하였다. 실습에 localhost는 사용하기 힘들기 때문에 etc/hosts 설정을 통해 로컬을 특정 도메인처럼 할당해보자.

 

> sudo vi /etc/hosts
127.0.0.1 levi.local.com

 

이제 ingress-nginx가 포워딩할 웹어플리케이션 팟을 띄워보자.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: sample-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: springboot-web
  template:
    metadata:
      labels:
        app: springboot-web
    spec:
      containers:
        - name: springboot-web
          image: 1223yys/springboot-web:0.2.5
          imagePullPolicy: Always
          ports:
            - containerPort: 8080
          livenessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60
          readinessProbe:
            httpGet:
              port: 8080
              path: /api
            initialDelaySeconds: 60

 

디플로이먼트 컨트롤러로 springboot-web 애플리케이션 팟을 관리하도록 매니페스트를 작성하였다. 해당 매니패스트를 적용해보자.

 

> kubectl apply -f deployment.yaml

 

다음으로는 springboot-web으로 접근할 수 있게 해주는 외부 통로인 서비스를 작성해보자.

 

apiVersion: v1
kind: Service
metadata:
  name: springboot-web-service
spec:
  selector:
    app: springboot-web
  ports:
    - name: http
      port: 80
      targetPort: 8080

 

해당 서비스도 배포해보자.

 

> kubectl apply -f service.yaml

 

마지막으로 ingress nginx 매니페스트 파일을 작성해보자.

 

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx-ingress-sample
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: levi.local.com
    http:
      paths:
      - path: /
        backend:
          serviceName: springboot-web-service
          servicePort: 80

 

위 매니페스트 파일에서 host를 유의하자. 포스팅 초반에 localhost를 로컬 도메인으로 할당해야한다 했는데, 그 이유가 위 host 때문이다. 위에 보이는 host는 실제로 http 요청이 들어올때 요청 헤어의 "Host : levi.local.com" 을 참조하기 때문이다.

 

이제 요청을 보내보자.

 

> curl http://levi.local.com:30431/api
new api !


 ingress-nginx를 통해 웹앱 팟에 잘 접근되는 것을 확인할 수 있다.

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 7. 19. 21:41

 

오늘 간단히 다루어볼 내용은 쿠버네티스 리소스(cpu, memory) 할당과 관리에 대한 이야기이다.  

 

리소스 관리

쿠버네티스에서 Pod를 어느 노드에 배포할지 결정하는 것을 스케쥴링이라고 한다. 팟에 대한 스케쥴링시, 노드에 애플리케이션이 동작할 수 있는 충분한자원(CPU, 메모리 등)이 확보되어야 배포가 가능하다. 이때문에 쿠버네티스 manifast 파일에 아주 중요한 설정이 있는데, 그것은 request, limit 에 대한 설정이다.

 

Request&Limit

 

컨테이너에 적용될 리소스의 양을 정의하는데, request와 limit이라는 설정을 사용한다. request는 컨테이너가 생성될때 최소한 있어야하는 자원 요청이고, limit은 request만큼 할당된 것보다 더 많은 리소스가 필요할때, 해당 컨테이너에게 최대로 줄 수 있는 자원의 양을 뜻한다. 간단히 예를 들어보면, request가 500이고, limit이 1000 이라면, 컨테이너는 처음 시작될때 500을 할당 받고 실행되며, 많은 트래픽이 몰려 리소스가 부족하다면 최대 500만큼의 자원을 더 받을 수 있다.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-service
spec:
  replicas: 4
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2 //팟을 시작&종료할때 2개씩 작업한다.
      maxUnavailable: 0 //롤링 업뎃시 모든 팟(4개)이 서비스 가능하도록. 
                          만약 1이면, 리플리카 4개중 1개는 작업불능
  template:
    spec:
      containers:
      - name: web-service
        resources:
          requests:
            cpu: 2000m
            memory: 2Gi
          limits:
            cpu: 4000m
            memory: 4Gi

 

Request&Limit을 지정해야하는 이유는? Overcommitted 상태

이  request와 limit의 개념이 있기 때문에 생기는 문제인데, request 된 양에 따라서 컨테이너를 만들었다고 하더라도, 컨테이너가 운영이되다가 자원이 모자르면 limit 에 정의된 양까지 계속해서 리소스를 요청하게 된다. 컨테이너의 총 Limit의 양이 실제 시스템이 가용한 resource의 양보다 많을 수 있는 경우가 발생한다. 이를 overcommitted 상태라고 한다. Overcommitted 상태가 발생하면, CPU의 경우에는 실제 사용량을 requested 에 정의된 상태까지 낮춘다. 예를 들어 limit이 500, request가 100인 경우, 현재 500으로 가동되고 있는 컨테이너의 CPU할당량을 100으로 낮춘다. 그래도 Overcommitted 상태가 해결되지 않는 경우, 우선 순위에 따라서 운영중인 컨테이너를 강제 종료 시킨다. 메모리의 경우에는 할당되어 사용중인 메모리의 크기를 줄일 수 는 없기 때문에, 우선 순위에 따라서 운영 중인 컨테이너를 강제 종료 시킨다.  Deployment,RS/RC에 의해 관리되고 있는 컨테이너는 다시 리스타트가 되고 초기 requested 상태의 만큼만 자원 (메모리/CPU)를 요청해서 사용하기 때문에, overcommitted  상태가 해제된다.

 

Best practice

구글 문서에 따르면 데이타 베이스등 아주 무거운 애플리케이션이 아니면, 일반적인 경우에는 CPU request를 100m 이하로 사용하기를 권장한다. 또한 세밀하게 클러스터를 운영하기 어려운 경우에는 request와 limit의 사이즈를 같게 하는 것을 권장한다. limit이 request보다 클 경우 overcommitted 상태가 발생할 수 있는데, 이때 CPU가 throttle down 되면, 실제 필요한 CPU양 보다 작은 CPU양으로 줄어들기 때문에 성능저하가 발생할 수 있다.


<참조>

 

쿠버네티스 #21 - 리소스(CPU/Memory) 할당과 관리

쿠버네티스 리소스(CPU/Memory)할당과 관리 조대협 리소스 관리 쿠버네티스에서 Pod를 어느 노드에 배포할지를 결정하는 것을 스케쥴링이라고 한다. Pod에 대한 스케쥴링시에, Pod내의 애플리케이션�

bcho.tistory.com

 

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 6. 4. 17:10

이번 포스팅은 간단하게 싱글 노드 카프카를 도커로 띄우는 방법이다.

 

git clone https://github.com/wurstmeister/kafka-docker
cd kafka-docker

 

설정 파일은 docker-compoese로 되어있으며, 아래와 같다.

 

version: '2'
services:
  zookeeper:
    image: wurstmeister/zookeeper
    ports:
      - "2181:2181"
  kafka:
    build: .
    ports:
      - "9092:9092"
    environment:
      KAFKA_ADVERTISED_HOST_NAME: 127.0.0.1
      KAFKA_CREATE_TOPICS: "test:1:1"
      KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock

 

docker compose 명령으로 실제 컨테이너를 띄운다.

 

docker-compose -f docker-compose-single-broker.yml up -d

 

posted by 여성게
:
인프라/Docker&Kubernetes 2020. 2. 24. 00:25

오늘 다루어볼 내용은 쿠버네티스 환경에서의 로깅운영 방법이다. 지금까지는 쿠버네티스에 어떻게 팟을 띄우는지에 대해 집중했다면 오늘 포스팅 내용은 운영단계의 내용이 될 것 같다. 사실 어떻게 보면 가장 중요한 내용중에 하나라고 볼 수 있는 것이 로깅이다. 물리머신에 웹을 띄울 때는 파일로 로그를 날짜별로 남기고, 누적 일수이상된 파일은 제거 혹은 다른 곳으로 파일을 옮기는 등의 작업을 했을 것이다. 하지만 쿠버네티스에서는 파일로 로그를 남기지 않으며 조금 다른 방법으로 로깅운영을 진행한다. 

 

컨테이너 환경에서 로그를 운영하는 구체적인 방법을 설명하기 전에 컨테이너 환경에서 로그가 어떻게 생성되는지 알아본다. 비컨테이너 환경의 애플리케이션에서는 보통 로그를 파일로 많이 남기고 한다. 이에 비해 도커에서는 로그를 파일이 아닌 표준 출력으로 출력하고 이를 다시 Fluentd 같은 로그 컬렉터로 수집하는 경우가 많다. 이런 방법은 애플리케이션 쪽에서 로그 로테이션이 필요없으며 로그 전송을 돕는 로깅 드라이버 기능도 갖추고 있으므로 로그 수집이 편리하다.

 

간단하게 필자가 만든 이미지로 실습을 진행한다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
> docker pull 1223yys/springboot-web:0.2.5
 
> docker container run -it --rm -p 8080:8080 1223yys/springboot-web:0.2.5
 
  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.2.1.RELEASE)
 
2020-02-23 11:29:15.368  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : Starting SampleApplication on d1e5a6d24e34 with PID 1 (/app/app.jar started by root in /)
2020-02-23 11:29:15.375  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : No active profile set, falling back to default profiles: default
2020-02-23 11:29:17.099  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2020-02-23 11:29:17.122  INFO 1 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-02-23 11:29:17.124  INFO 1 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.27]
2020-02-23 11:29:17.244  INFO 1 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2020-02-23 11:29:17.245  INFO 1 --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1784 ms
2020-02-23 11:29:17.619  INFO 1 --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
2020-02-23 11:29:17.885  INFO 1 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2020-02-23 11:29:17.890  INFO 1 --- [           main] com.kebe.sample.SampleApplication        : Started SampleApplication in 3.221 seconds (JVM running for 3.936)
cs

 

 

yoonyeoseong/kubernetes-sample

Contribute to yoonyeoseong/kubernetes-sample development by creating an account on GitHub.

github.com

우선 이미지를 내려받고 애플리케이션을 포어그라운드로 실행시킨다. 그 다음 로그가 호스트에서 어떻게 출력되는지 확인해보자.

 

그전에 아래의 명령으로 실행 중인 컨테이너들의 로그가 어떻게 찍히는 지 확인할 수 있다.

 

1
> docker run -it --rm -v /var/lib/docker/containers:/json-log alpine ash
cs

 

명령 실행 후에 /json-log 디렉토리에 위에서 실행한 컨테이너의 아이디 디렉토리로 들어가면 현재 우리가 표준출력으로 찍고 있는 로그가 json 타입으로 찍히고 있으며 이 표준출력이 파일로 남고 있는 것을 볼 수 있다.

 

 

다시 말해, 애플리케이션에서 로그를 파일로 출력하지 않았더라도 도커에서 컨테이너의 표준 출력을 로그로 출력해주는 것이다. 따라서 로그 출력 자체를 완전히 도커에 맡길 수 있다.

 

도커 로깅 드라이버

도커 컨테이너의 로그가 JSON 포맷으로 출력되는 이유는 도커에 json-file이라는 기본 로깅 드라이버가 있기 때문이다. 로깅 드라이버는 도커 컨테이너가 출력하는 로그를 어떻게 다룰지를 제어하는 역할을 한다. json-file 외에도 다음과 같은 로깅 드라이버가 존재한다.

 

logging driver description
Syslog syslog로 로그를 관리
Journald systemd로 로그를 관리
Awslogs AWS CloudWatch Logs로 로그를 전송
Gcplogs Google Cloud Logging으로 로그를 전송
Fluentd fluentd로 로그를 관리

 

도커 로그는 fluentd를 사용해 수집하는 것이 정석이다. 

 

컨테이너 로그의 로테이션

애플리케이션에서 표준 출력으로 출력하기만 해도 로그를 파일에 출력할 수 있지만, 웹 애플리케이션처럼 컨테이너 업타임이 길거나 액세스 수에 비례해 로그 출력량이 늘어나는 경우에는 JSON 로그 파일 크기가 점점 커진다. 컨테이너를 오랜 시간 운영하려면 이 로그를 적절히 로테이션할 필요가 있다.

 

도커 컨테이너에는 로깅 동작을 제어하는 옵션인 --log-opt가 있어서 이 옵션으로 도커 컨테이너의 로그 로테이션을 설정할 수 있다. max-size는 로테이션이 발생하는 로그 파일 최대 크기이며 l/m/g 단위로 파일 크기 지정이 가능하다. max-file은 최대 파일 개수를 의미하며 파일 개수가 이 값을 초과할 경우 오래된 파일부터 삭제된다.

 

1
docker container run -it --rm -p 8080:8080 --log-opt max-size=1m --log-opt max-file=5 1223yys/springboot-web:0.2.5
cs

 

이 설정을 매번 컨테이너 실행마다 할 필요는 없고, 도커 데몬에서 log-opt를 기본값으로 설정할 수 있다. Preference 화면에서 Deamon > Advanced 항목에서 다음과 같이 JSON 포맷으로 설정할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
{
  "experimental" : false,
  "debug" : true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "1m",
    "max-file": "5"
  }
}
 
cs

 

쿠버네티스에서 로그 관리하기

다른 예제는 뛰어넘고, 바로 쿠버네티스 환경에서 로그관리하는 방법을 알아본다. 우선 가장 대중적인 쿠버네티스 로그 운영은 아래와 같은 플로우로 많이 진행하는 것 같다.

 

app ---> fluentd ---> elasticsearch ---> kibana

 

app에서는 표준 출력으로 로그를 출력하고 fluentd는 로그를 긁어서 엘라스틱서치에 색인한다. 그리고 키바나를 통해 색인된 로그들을 모니터링한다. 쿠버네티스의 로그 관리에서도 역시 컨테이너는 표준 출력으로만 로그를 내보내면 되며, 그에 대한 처리는 컨테이너 외부에서 이루어진다.

 

쿠버네티스는 다수의 도커 호스트를 노드로 운영하는데, 어떤 노드에 어떤 파드가 배치될지는 쿠버네티스 스케줄러가 결정한다. 그러므로 각 컨테이너가 독자적으로 로그를 관리하면 비용이 많이 든다. 먼저 로컬 쿠버네티스 환경에 Elasticsearch와 Kibana를 구축한 다음, 로그를 전송할 fluentd와 DaemonSet을 구축한다.

 

쿠버네티스에 Elasticsearch와 Kibana 구축하기

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: elasticsearch-pvc
  namespace: kube-system
  labels:
    kubernetes.io/cluster-service: "true"
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 2G
---
apiVersion: v1
kind: Service
metadata:
  name: elasticsearch
  namespace: kube-system
spec:
  selector:
    app: elasticsearch
  ports:
  - protocol: TCP
    port: 9200
    targetPort: http
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: elasticsearch
  namespace: kube-system
  labels:
    app: elasticsearch
spec:
  replicas: 1
  selector:
    matchLabels:
      app: elasticsearch
  template:
    metadata:
      labels:
        app: elasticsearch
    spec:
      containers:
      - name: elasticsearch
        image: elasticsearch:5.6-alpine
        ports:
        - containerPort: 9200
          name: http
        volumeMounts:
        - mountPath: /data
          name: elasticsearch-pvc
        - mountPath: /usr/share/elasticsearch/config
          name: elasticsearch-config
      volumes:
      - name: elasticsearch-pvc
        persistentVolumeClaim:
          claimName: elasticsearch-pvc
      - name: elasticsearch-config
        configMap:
          name: elasticsearch-config
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: elasticsearch-config
  namespace: kube-system
data:
  elasticsearch.yml: |-
    http.host: 0.0.0.0
    path.scripts: /tmp/scripts
  log4j2.properties: |-
    status = error
    appender.console.type = Console
    appender.console.name = console
    appender.console.layout.type = PatternLayout
    appender.console.layout.pattern = [%d{ISO8601}][%-5p][%-25c{1.}] %marker%m%n
    rootLogger.level = info
    rootLogger.appenderRef.console.ref = console
  jvm.options: |-
    -Xms128m
    -Xmx256m
    -XX:+UseConcMarkSweepGC
    -XX:CMSInitiatingOccupancyFraction=75
    -XX:+UseCMSInitiatingOccupancyOnly
    -XX:+AlwaysPreTouch
    -server
    -Xss1m
    -Djava.awt.headless=true
    -Dfile.encoding=UTF-8
    -Djna.nosys=true
    -Djdk.io.permissionsUseCanonicalPath=true
    -Dio.netty.noUnsafe=true
    -Dio.netty.noKeySetOptimization=true
    -Dio.netty.recycler.maxCapacityPerThread=0
    -Dlog4j.shutdownHookEnabled=false
    -Dlog4j2.disable.jmx=true
    -Dlog4j.skipJansi=true
    -XX:+HeapDumpOnOutOfMemoryError
cs

 

엘라스틱서치 manifast 파일이다. 볼륨마운트, 컨피그 맵 등의 설정이 추가적으로 들어갔다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
apiVersion: v1
kind: Service
metadata:
  name: kibana
  namespace: kube-system
spec:
  selector:
    app: kibana
  ports:
  - protocol: TCP
    port: 5601
    targetPort: http
    nodePort: 30050
  type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: kibana
  namespace: kube-system
  labels:
    app: kibana
spec:
  replicas: 1
  selector:
    matchLabels:
      app: kibana
  template:
    metadata:
      labels:
        app: kibana
    spec:
      containers:
      - name: kibana
        image: kibana:5.6
        ports:
        - containerPort: 5601
          name: http
        env:
        - name: ELASTICSEARCH_URL
          value: "http://elasticsearch:9200"
cs

 

키바나 manifast 파일이다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fluentd
  namespace: kube-system
  labels:
    app: fluentd-logging
    version: v1
    kubernetes.io/cluster-service: "true"
spec:
  selector:
    matchLabels:
      app: fluentd-logging
  template:
    metadata:
      labels:
        app: fluentd-logging
        version: v1
        kubernetes.io/cluster-service: "true"
    spec:
      tolerations:
      - key: node-role.kubernetes.io/master
        effect: NoSchedule
      containers:
      - name: fluentd
        image: fluent/fluentd-kubernetes-daemonset:elasticsearch
        env:
          - name: FLUENT_ELASTICSEARCH_HOST
            value: "elasticsearch"
          - name: FLUENT_ELASTICSEARCH_PORT
            value: "9200"
          - name: FLUENT_ELASTICSEARCH_SCHEME
            value: "http"
          - name: FLUENT_UID
            value: "0"
        resources:
          limits:
            memory: 200Mi
          requests:
            cpu: 100m
            memory: 200Mi
        volumeMounts:
        - name: varlog
          mountPath: /var/log
        - name: varlibdockercontainers
          mountPath: /var/lib/docker/containers
          readOnly: true
      terminationGracePeriodSeconds: 30
      volumes:
      - name: varlog
        hostPath:
          path: /var/log
      - name: varlibdockercontainers
        hostPath:
          path: /var/lib/docker/containers
cs

 

마지막으로 fluentd manifast이다. DaemonSet 으로 구성되며 클러스터 노드마다 할당되는 리소스이다. DaemonSet은 클러스터 전반적인 로깅등의 작업을 수행하는 리소스에 할당하는 타입이다. 어떠한 팟마다 같이 뜨는 리소스가 아니고 클러스터 노드마다 뜨는 리소스라고 보면 된다. 로그 컬렉터같이 호스트마다 특정할 역할을 하는 에이전트를 두고자 할 때 적합하다.

 

여기까지 간단하게 쿠버네티스 환경에서 로깅 운영하는 방법을 간단하게 다루어봤다. 다음 예제에서는 기본적인 플로우 이외로 output을 kafka로 보내는 등의 다른 플러그인을 사용하여 로그를 수집하는 예제를 살펴볼 것이다.

posted by 여성게
:
인프라/Docker&Kubernetes 2019. 11. 26. 23:24

 

지금까지 쿠버네티스에 대한 포스팅을 여러개 했는데, 더 진행하기 앞서 쿠버네티스에서 사용하는 용어 및 개념들을 정리하면 좋을듯 해서 포스팅한다.

 

마스터&노드

쿠버네티스의 클러스터의 구조에서 전체 클러스터를 관리하는 마스터 노드가 있고, 도커 컨테이너가 배포되는 노드가 존재한다.

 

 

Pod

Pod는 쿠버네티스에서 가장 기본적인 배포 단위로, 하나 이상의 도커 컨테이너의 묶음이다. 쿠버네티스는 컨테이너 단위로 배포하는 것이 아니라, Pod이라는 하나 이상의 도커 컨테이너로 이루어진 단위로 배포가 된다. 

 

그렇다면 개별적인 컨테이너로 배포하지 않고, Pod이라는 단위로 배포하는 이유는 무엇일까?

 

  • Pod 내의 컨테이너는 IP와 Port를 공유한다. 즉, 서로 localhost로 통신이 가능한 것이다.
  • Pod 내에 배포된 컨테이너 간에는 디스크 볼륨 공유가 가능하다. 

위와 같은 특징을 가지고 있기 때문에 보통 하나 이상의 컨테이너를 포함한 Pod 단위로 배포한다. 그렇다면 이러한 예제로는 어떤 것이 있을 까? 요즘 애플리케이션을 배포할 때는 애플리케이션 하나만 배포하는 것이 아니라, Nginx와 로그 수집기, 예거 등을 같이 배포하는 경우가 많은 데, 보통 이런 조합은 디스크를 공유해야 하거나 서로 HTTP 통신을 해야한다. 하지만 이럴때 서로 다른 컨테이너로 배포할 경우 굉장히 구성이 까다로워질 것이다. 즉, 하나의 Pod 내에 여러 컨테이너로 띄울 경우 위 문제가 어느정도 해결된다.

 

이렇게 애플리케이션과 애플리케이션이 사용하는 주변 프로그램을 같이 배포하는 패턴을 MSA(Micro Service Architecture)에서 사이드카패턴이라고 한다.

 

Volume

Pod가 뜰때, 컨테이너마다 로컬 디스크를 생성해서 기동되는데, 이 로컬 디스크의 경우 영구적이지 못하다. 즉, 컨테이너가 리스타트 되거나 새로 배포될때 로컬 디스크의 내용이 유실된다. 그런데 보통 데이터베이스와 같이 영구적으로 파일을 저장해야 하는 경우에는 컨테이너의 리스타트와 상관없이 파일을 영구적으로 저장해야 하는데, 이럴때 볼륨을 마운트해서 사용한다. 즉, Pod에 볼륨을 할당한다는 뜻이다. 이러한 볼륨은 하나의 Pod 내에서 여러 컨테이너가 공유가능하다는 특징을 가지고 있다.

 

Service

보통의 경우 하나의 Pod으로만 서비스를 제공하지 않는다. 보통은 여러개의 동일한 Pod을 띄워 로드밸런싱하여 서비스를 제공한다. 이럴 경우에 Pod은 동적으로 생성되고 삭제되고, 생성되는 IP는 그때그때 다르기 때문에 이러한 Pod의 디스커버리를 IP로 묶는 것을 쉽지 않다. 그래서 사용되는 것이 Service이고 이는 내부적으로 라벨과 라벨 셀렉터를 이용하여 가용 가능한 Pod을 디스커버리한다.

 

서비스를 정의할때, 어떤 Pod을 하나의 서비스라는 개념으로 묶을 것인지 정의하는데, 이를 라벨 셀렉터라고 한다. 각 Pod의 Object를 정의할 때, 메타정보에 라벨정보를 넣을 수 있는데, 이 라벨을 Service Object를 정의할때 라벨셀렉터에 넣어준다. 이러면 동일한 라벨을 같은 여러개의 같은 Pod이 동일한 Service 단위로 묶이게 되는 것이다.

 

 

Namespace

네임스페이스는 한 쿠버네티스 클러스터 내의 논리적인 분리단위이다. Object들을 네임스페이스 단위로 별도 생성 및 관리가 가능하고, 사용자의 권한 역시 이 네임스페이스 단위로 부여할 수 있다. 보통 개발/운영/테스트 환경을 간혹 네임스페이스 단위로 나누기도 하는데, 왠만하면 클러스터 자체를 분리하는 것이 좋다. 네임스페이스로 나눈다고 해서 물리적인 환경을 분리하는 것이 아니고 단순 논리적인 분리밖에 되지 않아 머신의 리소스를 같이 사용하기 때문이다.

 

Label

라벨은 쿠버네티스의 리소스를 선택하는데 사용된다. 각 리소스는 라벨을 가질 수 있고, 라벨 검색 조건에 따라 특정 라벨을 가진 리소스만 선택이 가능하다. 라벨은 metadata 부분에 키/값으로 정의 가능하며 하나의 리소스에는 하나 이상의 라벨을 가질 수 있다.

 

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

 

라벨 셀렉터를 사용하는 방법으로 2가지 방법을 제공한다. 하나는 Equality based selector와, Set based selector가 있다.

 

Equality based selector는 단순히 같고,다르다는 조건을 이용하여 라벨을 셀렉하는 방법이다.

  • env = dev
  • env != product

Set based selector는 집합 개념을 이용한다.

  • env in (dev,qa)
  • env notin (product,stage)

 

Controller

컨트롤러는 기본 오브젝트(Pod,Service,Volume,Namespace)들을 생성하고 관리하는 역할을 한다. 컨트롤러는 Replicaion Controller, Replication Set, DaemonSet, Job, StatefulSet, Deployment 들이 있다.

 

1) Replication Controller

Replication Controller는 Pod을 관리해주는 역할을 하는데, 지정된 숫자로 Pod을 기동시키고, 관리하는 역할을 한다. Replication Controller는 크게 3가지 파트로 구성되는데, Replica의 수, Pod Selector, Pod Template(Spec)으로 구성된다.

 

 

이미 기동된 Pod이 있다면 주의해야할 점이 있다. Pod이 이미 떠있는 상태에서 RC 리소스를 생성하고 그 Pod의 라벨이 RC와 라벨 셀렉터와 일치하면 Pod들은 새롭게 생성된 RC의 컨트롤을 받는다. 만약 해당 Pod들이 RC에서 정의한 replica 수보다 많으면, replica 수에 맞게 Pod들을 삭제하고, 모자르면 template(Spec)에 정의된 되로 Pod을 띄우게 된다. 하지만 여기서 주의할 점이 template(Spec)에 정의된 Pod 리소스 설정과 라벨셀렉터에 의해 픽된 Pod의 리소스 설정이 다르더라도 기존 Pod을 내렸다가 올리지 않는 다는 점이다. 이말은 즉, 싱크가 맞지 않는 형상이 다른 Pod이 뜰 가능성이 있다.

 

2) ReplicaSet

ReplicaSet은 RC의 새버전이라 생각하면 된다. RC는 Equality 기반 셀럭터를 사용하는데, ReplicaSet은 Set 기반 셀렉터를 이용한다.

 

3) Deployment

Deployment는 RC와 RS의 좀더 상위 개념이다. 셀제 운영에서는 RC,RS 보다 Deployment를 이용한다. Deployment를 설명하기 전에 배포 방식에 대한 이해가 필요하므로 설명한다.

 

 

위와 같은 RC와 Pod들이 있다고 생각하고 새로운 버전의 Pod을 띄우는 상황이라 가정하자. 보통이런 상황에서 Blue/Green 배포와 Rolling Update 배포 방식을 이용한다.

 

Blue/Green deployment

블루그린 배포 방식은 블루(예전) 버전으로 서비스 하고 있던 시스템은 그린(새로운)버전을 배포한 후, 트래픽을 블루에서 그린으로 한번에 돌리는 방식이다. 여러가지 방법이 있지만 가장 쉬운 방법은 새로운 RC와 Pod들을 모두 띄운 후에 Service의 트래픽을 한번에 새로운 Pod으로 돌리는 방법이다.

 

배포후 문제가 없다면 이전 RC와 Pod들을 내려준다.

 

Rolling update deployment

롤링 업데이트 배포 방식은 Pod을 하나씩 업그레이드 하는 방식이다. 이렇게 배포를 하려면 먼저 새로운 RC를 만든 후에, 기존 RC에서 replica 수를 하나 줄이고, 새로운 RC에는 replica 수를 하나 늘려준다. 이런식으로 하나 줄이고 하나늘리고 하는 방식으로 배포를 하게된다.

 

 

만약 배포가 잘못되었다면 기존 RC의 replica를 원래대로 올리고, 새버전의 replica 수를 0으로 만들어 롤백한다. 이런 롤링 업데이트 배포 방식을 RC 단위로도 가능하지만 여러가지 작업이 필요하다. 그래서 이러한 여러 과정을 추상화한 개념을 Deployment라고 보면 된다.

 

4) DaemonSet

DaemonSet은 Pod가 각각의 노드에서 하나씩만 돌게하는 형태로 Pod을 관리하는 컨트롤러이다. RC, RS 혹은 Deployment로 관리되는 Pod들은 클러스터 노드의 리소스 상황에 따라 비균등적으로 배포되는데, DS에 의해 관리되는 Pod는 모든 노드에 균등하게 하나씩만 배포된다. 보통 서버 모니터링 혹은 로그 수집용도로 많이 사용된다.

 

DS의 다른 특징중 하나는 특정 Node들에만 Pod들을 하나씩 배포되도록 설정이 가능 하다. 이렇게 특정 노드를 셀렉하기 위해 node selector를 제공하여 특정 노드를 선택가능하도록 지원한다.

 

 

 

여기까지 간단히 쿠버네티스에서 사용되는 용어들을 다루어봤다. 사실 다루지 못한 용어들이 많이 있지만 추후에 더 다루어볼 예정이다. 다음 포스팅에서는 쿠버네티스 아키텍쳐에 대해 다루어볼 것이다.

posted by 여성게
: