5장 지속적 통합과 배포 자동화, 젠킨스

5.2.2 커스터마이즈로 배포 간편화하기

커스터마이즈: 운영 중인 환경에서 배포 시 가변적인 요소를 적용하는 데 적합

여러 yaml을 한 번에 바꾸려면 kustomize 명령을 사용할 수 있다. create 옵션으로kustomizaion.yaml이라는 기본 매니페스트를 만들고 이 파일에 변경해야 하는 값들을 적용한다.

커스터마이즈로 MetalLB 한 번에 만들기

커스터마이즈 설치

$ ~/_Book_k8sInfra/ch5/5.2.2/kustomize-install.sh
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100 12.4M  100 12.4M    0     0  3468k      0  0:00:03  0:00:03 --:--:-- 4777k
kustomize install successfully

커스터마이즈에서 리소스 및 주소 할당 영역을 구성할 때 사용할 파일을 확인하기 위해 디렉토리 이동 커스터마이즈로 변경될 작업을 정의하기 위해 kustomization.yaml 생성

$ cd ~/_Book_k8sInfra/ch5/5.2.2
$ ls
kustomize-install.sh  metallb-l2config.yaml  metallb.yaml  namespace.yaml

$ kustomize create --namespace=metallb-system --resources namespace.yaml,metallb.yaml,metallb-l2config.yaml

확인

$ cat kustomization.yaml 
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- metallb.yaml
- metallb-l2config.yaml
namespace: metallb-system

설치된 이미지를 안정 버전으로 유지하기 위해 버전 지정

$ kustomize edit set image metallb/controller:v0.8.2
$ kustomize edit set image metallb/speaker:v0.8.2

확인

$ cat kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- metallb.yaml
- metallb-l2config.yaml
namespace: metallb-system
images:
- name: metallb/controller
  newTag: v0.8.2  ##
- name: metallb/speaker
  newTag: v0.8.2  ##

build 명령으로 MetalLB 설치를 위한 매니페스트 생성

$ kustomize build
apiVersion: v1
kind: Namespace
metadata:
...
data:
  config: |
    address-pools:
    - name: metallb-ip-range
      protocol: layer2
      addresses:
      - 192.168.1.11-192.168.1.19 ##
...
        - --config=config
        image: metallb/controller:v0.8.2  ##
...
        image: metallb/speaker:v0.8.2  ##
        imagePullPolicy: IfNotPresent


빌드한 결과가 바로 kubectl apply에 인자로 전달해서 배포

$ kustomize build | kubectl apply -f -
namespace/metallb-system unchanged
serviceaccount/controller unchanged
serviceaccount/speaker unchanged
podsecuritypolicy.policy/speaker unchanged
role.rbac.authorization.k8s.io/config-watcher unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrole.rbac.authorization.k8s.io/metallb-system:speaker unchanged
rolebinding.rbac.authorization.k8s.io/config-watcher configured
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:controller unchanged
clusterrolebinding.rbac.authorization.k8s.io/metallb-system:speaker unchanged
configmap/config configured
deployment.apps/controller unchanged
daemonset.apps/speaker unchanged

확인

$ kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-5f98465b6b-69g5x   1/1     Running   0          5h45m
speaker-7mntj                 1/1     Running   0          5h45m
speaker-hbv6t                 1/1     Running   0          5h45m
speaker-rpbtr                 1/1     Running   0          5h45m
speaker-tdglq                 1/1     Running   0          5h45m
$ kubectl get configmap -n metallb-system
NAME     DATA   AGE
config   1      25h
$ kubectl describe pods -n metallb-system | grep Image:
    Image:         metallb/controller:v0.8.2
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
$ kubectl create deployment echo-ip --image=sysnet4admin/echo-ip
deployment.apps/echo-ip created

$ kubectl expose deployment echo-ip --type=LoadBalancer --port=80
service/echo-ip exposed

$ kubectl get service echo-ip
NAME      TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
echo-ip   LoadBalancer   10.106.119.99   192.168.1.22   80:31677/TCP   12s
$ kustomize build | kubectl delete -f -
namespace "metallb-system" deleted
serviceaccount "controller" deleted
serviceaccount "speaker" deleted
podsecuritypolicy.policy "speaker" deleted
role.rbac.authorization.k8s.io "config-watcher" deleted
clusterrole.rbac.authorization.k8s.io "metallb-system:controller" deleted
clusterrole.rbac.authorization.k8s.io "metallb-system:speaker" deleted
rolebinding.rbac.authorization.k8s.io "config-watcher" deleted
clusterrolebinding.rbac.authorization.k8s.io "metallb-system:controller" deleted
clusterrolebinding.rbac.authorization.k8s.io "metallb-system:speaker" deleted
configmap "config" deleted
deployment.apps "controller" deleted
daemonset.apps "speaker" deleted
$ kubectl delete service echo-ip
service "echo-ip" deleted

$ kubectl delete deployment echo-ip
deployment.apps "echo-ip" deleted

5.2.3 헬름으로 배포 간편화하기

헬름은 쿠버네티스 전용 패키지 매니저다. 패키지는 실행 파일뿐만 아니라 실행 환경에 필요한 의존성 파일과 환경 정보들의 묶음이다. 패키지 매니저는 외부에 있는 저장소에서 패키지 정보를 받아와 패키지를 안정적으로 관리하는 도구다.

헬름을 사용하면 요구 조건별로 리소스를 편집하거나 변수를 넘겨서 처리하는 패키지를 만들 수 있다. 이러한 요구 조건을 처리할 수 있는 패키지를 차트라고 한다. 이를 헬름 저장소에 공개해 여러 사용자와 공유한다.

헬름 기본 저장소는 아티팩트허브(artifacthub.io)로, 다른 패키지 매니저처럼 외부에 있다. 다른 저장소와 달리 아티팩트허브에서는 설치할 패키지에 대한 경로만 제공한다. 추후 헬름을 좀 더 알아볼 때 이 부분이 큰 차이점이 되므로 인지하고 있어야 한다.

헬름으로 MetalLB 한 번에 만들기

헬름 설치, 환경 변수를 설정해 헬름 버전을 3.2.1로 고정

$ export DESIRED_VERSION=v3.2.1; ~/_Book_k8sInfra/ch5/5.2.3/helm-install.sh 
Downloading https://get.helm.sh/helm-v3.2.1-linux-amd64.tar.gz
Verifying checksum... Done.
Preparing to install helm into /usr/local/bin
helm installed into /usr/local/bin/helm

https://artifacthub.io 에서 metallb 검색해 주소 확인

저자가 만든 저장소로 아티팩트허브에는 이 경로만을 표시한다.

$ helm repo add edu https://iac-source.github.io/helm-charts
"edu" has been added to your repositories
$ helm repo list
NAME    URL                                     
edu     https://iac-source.github.io/helm-charts
$ helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "edu" chart repository
Update Complete. ⎈ Happy Helming!⎈ 

등록, 업데이트한 저장도 edu로부터 MetalLB 설치

  • --create-namespce: 네임스페이스 옵션으로 지정된 네임스페이스가 존재하지 않으면 네임스페이스를 생성
  • --set: 헬름에서 사용할 변수를 명령 인자로 전달.
  • 헬름에서는 가변적인 인자를 사용하기 위해 --set 이후에 인자를 사용한다.
$ helm install metallb edu/metallb \
--namespace=metallb-system \
--create-namespace \
--set controller.tag=v0.8.3 \
--set configmap.ipRange=172.31.0.100-172.31.0.110

NAME: metallb
LAST DEPLOYED: Thu Feb 16 17:53:22 2023
NAMESPACE: metallb-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
MetalLB load-balancer is successfully installed.
1. IP Address range 192.168.1.11-192.168.1.29 is available.
2. You can create a LoadBalancer service with following command below.
kubectl expose deployment [deployment-name] --type=LoadBalancer --name=[LoadBalancer-name] --port=[external port]

확인

$ kubectl get pods -n metallb-system
NAME                          READY   STATUS    RESTARTS   AGE
controller-85478cc585-q7hwc   1/1     Running   0          3m2s
speaker-bl484                 1/1     Running   0          3m2s
speaker-mbfk5                 1/1     Running   0          3m2s
speaker-tgf7z                 1/1     Running   0          3m2s
speaker-vvw78                 1/1     Running   0          3m2s

$ kubectl get configmap -n metallb-system
NAME     DATA   AGE
config   1      3m36s
$ kubectl describe pods -n metallb-system | grep Image:
    Image:         metallb/controller:v0.8.3
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
    Image:         metallb/speaker:v0.8.2
 helm uninstall metallb -n metallb-system
 kubectl delete namespace metallb-system
 kubectl delete svc echo-ip
 kubectl delete deploy echo-ip
 
helm install metallb edu/metallb \
--namespace=metallb-system \
--create-namespace \
--set controller.tag=v0.8.3 \
--set configmap.ipRange="172.31.15.224/27"
 
 kubectl create deploy echo-ip --image=sysnet4admin/echo-ip
 kubectl expose deploy echo-ip --type=LoadBalancer --port=80
 kubectl get svc --all-namespaces

6장 안정적인 운영을 완성하는 모니터링, 프로메테우스와 그라파나

프로메테우스: 수집, 수집한 정보를 한곳에 통합 그라파나: 시각화

6.1.2 쿠버네티스 환경에 적합한 모니터링 데이터 수집방법

kubelet에 내장된 cAdvisor가 파드의 CPU나 메모리 같은 메트릭 정보를 수집한다. 데이터를 외부에서 모아서 표현해주는 도구 구성을 리소스 메트릭 파이프라인이라고 한다. 하지만 메트릭서버는 데이터를 메모리에만 저장해서 데이터를 영구적으로 보존하기 어렵다. 이에 따로 저장 공간에 저장하는 파이프라인이 권장되고 이 설계 방식을 구현한 도구가 프로메테우스다.

6.2 프로메테우스로 모니터링 데이터 수집과 통합하기

프로메테우스 서버

  • 1) 노드 익스포터 외 다른 여러 대상에서 공개된 메트릭을 수집해오는 수집기
  • 2) 수집한 시계열 메트릭 데이터를 저장하는 시계열 데이터베이스
  • 3) 저장된 데이터를 질의하거나 수집 대상의 상태를 확인할 수 있는 웹 UI

노드 익스포터

노드 시스템 메트릭 정보를 HTTP로 공개하는 역할이다.

쿠버 스테이트 메트릭

API 서버로 메트릭 데이터를 수집하고 이를 프로메테우스 서버가 수집할 수 있는 메트릭 데이터로 변환해 공개하는 역할이다.

얼럿매니저

프로메테우스에 경보 규칙을 설정, 경보 이벤트 발생 시 메시지를 전달한다.

푸시게이트웨이

배치와 스케줄 작업 시 수행되는 일회성 작업들의 상태를 저장하고 모아서 프로메테우스가 주기적으로 가져갈 수 있도록 공개한다.

6.2.1 헬름으로 프로메테우스 설치하기

NFS 디렉터리(/nfs_shared/prometheus)를 만들고 만든 NFS 디렉터리를 쿠버네티스 환경에서 사용할 수 있도록 PV와 PVC로 구성해야 한다.

헬름으로 프로메테우스 설치

쿠버네티스에 프로메테우스를 설치하는데 필요한 사전 구성

$  ~/_Book_k8sInfra/ch6/6.2.1/prometheus-server-preconfig.sh
[Step 1/4] Task [Check helm status]
[Step 1/4] ok
[Step 2/4] Task [Check MetalLB status]
[Step 2/4] ok
[Step 3/4] Task [Create NFS directory for prometheus-server]
/nfs_shared/prometheus/server created
[Step 3/4] Successfully completed
[Step 4/4] Task [Create PV,PVC for prometheus-server]
persistentvolume/prometheus-server created
persistentvolumeclaim/prometheus-server created
[Step 4/4] Successfully completed

프로메테우스 차트 설치

$ ~/_Book_k8sInfra/ch6/6.2.1/prometheus-install.sh
NAME: prometheus
LAST DEPLOYED: Thu Feb 16 18:06:04 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-server.default.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running 'kubectl get svc --namespace default -w prometheus-server'

  export SERVICE_IP=$(kubectl get svc --namespace default prometheus-server -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:80


#################################################################################
######   WARNING: Pod Security Policy has been moved to a global property.  #####
######            use .Values.podSecurityPolicy.enabled with pod-based      #####
######            annotations                                               #####
######            (e.g. .Values.nodeExporter.podSecurityPolicy.annotations) #####
#################################################################################
  
For more information on running Prometheus, visit:
https://prometheus.io/

prometheus-install.sh

$  kubectl get pods --selector=app=prometheus
NAME                                             READY   STATUS    RESTARTS   AGE
prometheus-kube-state-metrics-7bc49db5c5-qzbwh   1/1     Running   0          3m4s
prometheus-node-exporter-9qsbx                   1/1     Running   0          3m4s
prometheus-node-exporter-k5rpl                   1/1     Running   0          3m4s
prometheus-node-exporter-lfx4n                   1/1     Running   0          3m4s
prometheus-node-exporter-qjqh5                   1/1     Running   0          3m4s
prometheus-server-6d77896bb4-b4zn6               2/2     Running   0          3m4s
#!/usr/bin/env bash  
helm install prometheus edu/prometheus \  
--set pushgateway.enabled=false \  
--set alertmanager.enabled=false \  
--set nodeExporter.tolerations[0].key=node-role.kubernetes.io/master \  
--set nodeExporter.tolerations[0].effect=NoSchedule \  
--set nodeExporter.tolerations[0].operator=Exists \  
--set server.persistentVolume.existingClaim="prometheus-server" \  
--set server.securityContext.runAsGroup=1000 \  
--set server.securityContext.runAsUser=1000 \  
--set server.service.type="LoadBalancer" \  
--set server.extraFlags[0]="storage.tsdb.no-lockfile"

프로메테우스 관련 파드 설치 확인

$ kubectl get pods --selector=app=prometheus
NAME                                             READY   STATUS    RESTARTS   AGE
prometheus-kube-state-metrics-7bc49db5c5-vmr67   1/1     Running   0          53s
prometheus-node-exporter-2jkgr                   1/1     Running   0          53s
prometheus-node-exporter-fpqtw                   1/1     Running   0          52s
prometheus-node-exporter-m2bhh                   1/1     Running   0          53s
prometheus-node-exporter-m7vxn                   1/1     Running   0          53s
prometheus-server-6d77896bb4-bnx9q               1/2     Running   0          53s
$ kubectl get service prometheus-server
NAME                TYPE           CLUSTER-IP      EXTERNAL-IP    PORT(S)        AGE
prometheus-server   LoadBalancer   10.100.40.120   192.168.1.11   80:31596/TCP   3m44s

브라우저에서 접속: http://192.168.1.11

6.2.3 서비스 디커버리로 수집 대상 가져오기

서비스 디스커버리는 프로메테우스 서버가 수집 대상을 가져오는 방법이다.

  • 1) 프로메테우스 서버는 컨피그맵에 기록된 내용을 바탕으로 대상을 읽어온다.
  • 2) 읽어온 대상에 대한 메트릭을 가져오기 위해 API 서버에 정보를 요청한다.
  • 3) 요청을 통해 알아온 경로로 메트릭 데이터를 수집한다.

cAdvisor: 쿠버네티스 API 서버에 직접 연결돼 메트릭을 수집 에이전트(익스포터): API 서버가 경로를 알려줘 메트릭 수집

cAdvisor

1) 웹 UI의 Graph 메뉴 쿼리 입력기에 container_memory_usage_bytes 입력하고 Execute 버튼 누름.

Load time: 27ms
Resolution: 14s (수집된 데이터로 지정된 초 단위의 그래프를 그린다.) Total time series: 150 (PromQL로 수집된 결과의 개수)

2) {container="nginx"}를 추가한다. 이런 구문을 레이블이라고 하는데 PromQL에서 필요한 내용을 추출할 때 사용한다. container_memory_usage_bytes{container="nginx"} 현재 nginx 디플로이먼트가 설치돼 있지 않아서 아무것도 검색되지 않는다.

3) nginx 배포하고 1~2분 기다린다.

$ kubectl create deployment nginx --image=nginx
deployment.apps/nginx created
$ kubectl get configmap prometheus-server -o yaml | nl

4) 다시 Execute 버튼 눌러서 수집 확인

$ kubectl get configmap prometheus-server -o yaml | nl

    56        job_name: kubernetes-nodes-cadvisor
    57        kubernetes_sd_configs:
    58        - role: node
    59        relabel_configs:
    60        - action: labelmap
    61          regex: __meta_kubernetes_node_label_(.+)
    62        - replacement: kubernetes.default.svc:443
    63          target_label: __address__
    64        - regex: (.+)
    65          replacement: /api/v1/nodes/$1/proxy/metrics/cadvisor
    66          source_labels:
    67          - __meta_kubernetes_node_name
    68          target_label: __metrics_path__
$ kubectl delete deployment nginx
deployment.apps "nginx" deleted

익스포터

서비스 디스커버리에서 수집은 자동이다. 하지만 익스포터는 사전 준비 작업 2가지를 해야 한다. 첫 번째로 API 서버에서 등록돼 경로를 알 수 있게 해야 하고 두 번째로 익스포터가 데이터를 프로메테우스 타입으로 노출해야 한다.

애너테이션으로 메트릭 수집

애너테이션: 주석으로 추가되는 메타데이터다. 프로메테우스는 애너테이션을 이용해 수집 대상을 판별하고 경로를 재조합한다.

  • 1) 프로메테우스에 메트릭을 공개하려는 애플리케이션은 애너테이션이 매니패스트에 추가돼 있어야 한다. 이때 애너테이션에 추가되는 구문은 prometheus.io/로 시작한다.
  • 2) 작성된 매니페스트를 쿠버네티스 클러스터에 배포해 애너테이션을 포함한 정보를 API 서버에 등록한다.
  • 3) 프로메테우스 서버가 prometheus.io/로 시작하는 애너테이션 정보를 기준으로 대상의 주소를 만든다.
  • 4) 프로메테우스 서버가 애너테이션을 기준으로 만든 대상의 주소로 요청을 보내 메트릭 데이터를 수집한다.

API 서버에 등록될 구성이 포함된 애너테이션 파일을 배포한다. 이 애너테이션은 프로메테우스 서버가 API 서버에서 애플리케이션을 찾아 메트릭을 수집할 때 사용한다.

$ kubectl apply -f ~/_Book_k8sInfra/ch6/6.2.3/nginx-status-annot.yaml
deployment.apps/nginx created
$ cat ~/_Book_k8sInfra/ch6/6.2.3/nginx-status-annot.yaml | nl
 annotations:
    prometheus.io/port: "80"
    prometheus.io/scrape: "true"

하지만 애너테이션에 설정만 한다고 메트릭이 수집되지는 않는다. 메트릭이 공개되지 않았기 때문인데 프로메테우스에서는 1. 프로그래밍 언어의 SDK를 사용해 메트릭을 공개하거나 2. 이미 만들어둔 익스포터를 사용해 메트릭을 공개해야 한다. 아래는 2번째 방법이다.

NGINX에서 제공하는 nginx-prometheus-exporter를 추가로 구성한 nginx-status-metrics.yaml을 배포한다.

$ kubectl apply -f ~/_Book_k8sInfra/ch6/6.2.3/nginx-status-metrics.yaml 
deployment.apps/nginx configured

nginx-prometheus-exporter는 멀티 컨테이너 패턴 중에 사이드카 패턴으로 작성돼 있다.

멀티 컨테이너 패턴

  • 사이드카(Sidecar): 메인 컨테이너의 기능을 확장하거나 기능을 향상하고자 할 때 추가하는 패턴
  • 앰배서더(Ambassador): 메인 컨테이너의 네트워크 연결을 전담하는 프록시 컨테이너를 두는 패턴.
  • 어댑터(Adapter): 메인 컨테이너의 출력을 표준화한다. 메인 컨테이너의 정보를 외부에서 사용할 수 있는 형식으로 변환하는 컨테이너가 추가된 형태다. 데이터 형태를 변환하므로 앞에서 다룬 NGINX의 익스포터는 어댑터 패턴이라고도 할 수 있다
$ kubectl delete -f ~/_Book_k8sInfra/ch6/6.2.3/nginx-status-metrics.yaml 
deployment.apps "nginx" deleted

6.2.4 노드 익스포터로 쿠버네티스 노드 메트릭 수집하기

노드 익스포터는 노드의 /proc/sys에 있는 값을 메트릭으로 노출하도록 구현돼 있다. 노드 익스포터를 실행하기 위해 쿠버네티스 노드마다 1개씩 배포할 수 잇는 데몬셋으로 구현돼 배포된다.

  • /proc: 리눅스 운영체제에서 구동 중인 프로세스들의 정보를 파일 시스템 형태로 연결한 디렉터리다. 디렉터리 안에는 현재 구동 중인 프로세스들의 프로세스 ID가 디렉터리 형태로 나타나며 각 디렉터리 내부에 해당 프로세스에 대한 상태나 실행 환경에 대한 정보가 들어 있다. 모니터링 도구는 이 디렉터리를 통해 시스템에서 구동 중인 프로세스의 정보나 상태, 자원 사용량 등을 알 수 있다.
  • /sys: 저장 장치, 네트워크 장치, 입출력 장치 같은 각종 장치를 운영 체제에서 사용할 수 있도록 파일 시스템 형태를 연결한 디렉터리다. 기존 /proc에 연결돼 있던 커널 장치 드라이버 등이 /proc에서 /sys로 분리됐다. 이 디렉터리 내부에 있는 파일들을 분석하면 장치의 상태를 알 수 있기 때문에 모니터링 도구는 이 디렉터리를 시스템 장치의 상태 정보를 수집하는 데 사용한다.

cAdvisor의 메트릭은 container로 시작하고 노드 익스포터의 메트릭은 node로 시작한다.

각 노드의 특정 디렉터리를 마운트해서 사용한다.

노드 익스포터로 수집된 메트릭 확인하기

웹 UI Graph 메뉴로 이동. 쿼리 입력기에

  • node_cpu_seconds_total: 노드의 CPU 상태별 사용된 시간
  • node_memory_MemAvailable_bytes: 노드별로 현재 사용 가능한 메모리의 용량

6.2.5 쿠버 스테이트 메트릭으로 쿠버네티스 클러스터 메트릭 수집하기

쿠버 스테이트 메트릭으로 수집된 메트릭 수집하기

  • 쿠버스테이트 메트릭은 kube로 시작
  • cAdvisor: 쿠버네티스 API 서버에 직접 연결돼 메트릭을 수집
    • container로 시작 (예: container_memory_usage_bytes{container="nginx"})
  • 에이전트(익스포터): API 서버가 경로를 알려줘 메트릭 수집
    • 노드 익스포터: node로 시작(예: node_cpu_seconds_total)

프로메테우스 쿼리 입력기에 입력

  • kube_pod_container_status_restarts_total : 배포된 파드가 다시 시작하는 경우 이를 누적 기록한 메트릭 데이터
  • kube_service_created: 쿠버네티스 현재 상태를 알아보는 메트릭 검색

cAdvisor

  • 컨테이터 정보 수집
  • container로 시작

노드 익스포터

  • 프로세스, 시스템 정보 수집
  • node로 시작
  • 노드 익스포터는 쿠버네티스 노드의 /proc, /sys에 있는 파일들을 읽어서 프로메테우스가 받아들일 수 있는 메트릭 형태로 변환한다. 각 노드의 특정 디렉터리를 마운트해서 사용한다.

쿠버 스테이트 메트릭

  • 쿠버네티스 상태 수집
  • kube로 시작

6.3 PromQL로 메트릭 데이터 추출하기

현업에서는 수집된 메트릭 데이터를 그대로 사용하기보다 디사 한 번 가공하는 경우가 많은데 이때 PromQL을 사용한다.

6.3.1 메트릭 데이터의 구조

up{job="prometheus"}

  • up은 수집대상이 작동하고 있는지 알려준다.
  • up이라는 메트릭 이름을 가지는 대상을 검색
  • 그 중에 {job="prometheus"}라는 레이블 이름을 검색 조건에 추가

  • 위와 같은 작업을 필터링이라고 하고 추출에 성공하면 Value에 1로 표현된다.

메트릭 값의 종류

  • 누적되는 메트릭 데이터 값, 카운터 타입: node_cpu_seconds_total, kube_pod_container_status_restarts_total
  • 특정 시점의 메트릭 데이터 값, 게이지 타입: node_memory_MemAvailable_bytes, kube_service_created

  • 카운터: 누적된 값을 표현하는 메트릭 타입. 변화율을 파악해 추세를 파악하기 용이하다. 이벤트 오류 등이 급증하는 구간을 파악하는 데 적합하다.
  • 게이지: 특정 시점의 값을 표현하는 데 사용하는 메트릭 타입. 시점별로 증가나 감소를 모두 표현할 수 있다.
  • 히스토그램: 사전에 미리 정의한 구간 안에 있는 메트릭 값의 빈도를 측정. 이때 익스포터를 구현하는 단계에서 정의한 구간을 버킷이라고 한다. 예를 들어 히스토그램을 사용해 클라이언트가 서버로 HTTP 요청을 한 경우 응답 시간과 맞는 버킷에 값을 추가하고 이벤트 횟수를 저장해 표시할 수 있다.
  • 서머리: 히스토그램과 비슷하게 구간 내 메트릭 값의 빈도를 측정한다. 예를 들어 클라이언트 요청에 따른 응답 시간을 관측하고 저장할 때 사용할 수 있다. 하지만 히스토그램과 달리 구간이 지정되는 것이 아니라 프로메테우스 자체로 0~1사이 구간을 미리 정해 놓는다.

total로 끝나면 누적한 값이므로 카운터 타입 bytes 또는 created로 끝나면 해당 시점의 용량 또는 생성됨을 의미하므로 게이지 타입

좀더 정확한 메트릭 값의 타입은 각 익스포터에서 공개되는 메트릭 정보를 curl로 조회해 다름과 같이 메트릭 위에 주석으로 확인할 수 있다.

$ curl -s 192.168.1.10:9100/metrics | nl | grep node_memory_MemAvailable_bytes
   367  # HELP node_memory_MemAvailable_bytes Memory information field MemAvailable_bytes.
   368  # TYPE node_memory_MemAvailable_bytes gauge
   369  node_memory_MemAvailable_bytes 1.915121664e+09

메트릭 레이블

모든 메트릭 데이터는 하나 이상의 레이블을 가진다. 프로메테우스의 레이블은 일반적인 주석이 아니라 메트릭 데이터의 다양한 내용을 표현하는 유일한 방법이다.

따라서 단순히 1~2개의 레이블이 아니라 제공하고 싶은 다수의 내용을 key-value 형태로 넣는다. 이렇게 제공되는 다수의 레이블로 관리자는 원하는 레이블을 검색하고 선택적으로 추출할 수 있다.

up{job="prometheus"}의 메트릭 데이터에는 instance="localhost:9090"job="prometheus"라는 2개의 레이블만 있지만, up{job="kubernetes-nodes}"로 검색하면 7개의 레이블을 확인할 수 있다.

메트릭 레이블 매처

메트릭 레이블에 조건을 줘서 검색하는 방법을 레이블 매처라고 한다.

  • =: {instance="m-k8s"}
  • !=: node_memory_MemAvailable_bytes{kubernetes_node!="m-k8s"}
  • =~: 조건에 넣은 정규 표현식에 해당하는 메트릭을 보여준다. {instance=~"w.+"}는 instance 레이블 값이 w로 시작하는 메트릭을 찾아 출력
  • !~: 조건에 넣은 정규 표현식에 해당하지 않는 메트릭을 보여준다.

비교 연산자

다시 시작한 적이 있는 파드만 검색: kube_pod_container_status_restarts_total > 0

논리 연산자

and 양쪽 모두 쿠버네티스 노드가 정상인지 파악: sum(up{job="kubernetes-nodes"}) == 4 and avg(up{job=="kubernetes-nodes"}) == 1

산술 연산자

node_memory_MemAvailable_bytes/1024/1024/1024

집계 연산자

avg(node_cpu_seconds_total{mode="idle"}) by (kubernetes_node)

6.3.3 PromQL 데이터 타입

시계열 정보가 담겨 있는 메트릭 데이터 node_cpu_seconds_total처럼 PromQL 표현에 맞는 쿼리를 입력해도 시간 정보는 나타나지 않는다. 시간 포함한 메트릭 데이터를 확인하려면 구간 값을 입력해야 한다.

메트릭 데이터를 받아오는 구간을 프로메테우스에서는 레인지 셀렉터라고 한다.

node_cpu_seconds_total[5m]: 5분 동안 발생된 메트릭 값

  • 레인지 셀렉터: 메트릭 데이터를 받아오는 구간(ms(밀리초), s(초), h(시간), d(일), w(주), y(년))
  • 레인지 벡터: 레인지 셀렉터와 같이 구간이 있는 타입
  • 인스턴트 벡터: 특정 시점(보통 현재)에 대한 메트릭 값만 가지는 타입
  • 스칼라 타입: 실수 값을 표현

레인지 벡터와 인스턴스 벡터 비교하기

인스턴스 벡터: node_cpu_seconds_total{mode="idle", kubernetes_node="w3-k8s"} 레인지 벡터: node_cpu_seconds_total{mode="idle", kubernetes_node="w3-k8s"}[5m]

카운터

  • 누적값
  • total로 끝난다.

게이지

  • 특정시점 값
  • bytes로 끝난다.

시계열 메트릭 데이터로 그래프 그리기

왜 레인지 벡터, 인스턴트 벡터를 쓰나? 데이터 제공 값이 게이지가 아닌 카운터만 지원하는데 그래프에 표시할 때 누적값이 아니라 특정시점 값을 제공해야 하므로 이런 경우 레인지 벡터로 구성된 값들의 변화를 계산해 변화율을 그래프 형태로 그려야 한다.

카운터 타입은 항상 증가하는 값이므로 그래프로 그리면 어떠한 의미도 알아낼 수 없다. 데이터의 흐름을 보려면 게이지 타입의 메트릭을 사용해야 하는데 메트릭 값에 따라 카운터 타입만을 제공하는 경우가 있다. 대표적인 예가 node_cpu_seconds_total이다. 이러한 경우에는 카운터 타입으로 구성된 레인지 벡터를 이용해야 한다. 레인지 벡터로 구성된 값들의 변화를 계산해 변화율을 그래프 형태로 그리는 것이다.

이러한 레인지 벡터의 변화율을 계산하려면 다른 연산 방법이 필요하다. 프로메테우스 내장 함수로 메메트릭 데이터를 분석하고 데이터를 변환하는 방법을 알아보자.

6.3.4 PromQL 함수

rate

  • 변화율
  • 구간 시작과 종료 값 차이에 대한 변화율

irate

  • 순간 변화율
  • 구간 종료 바로 전 값과 구간 종료 값의 차이에 대한 변화율

predict_linear

  • 레인지 벡터로 수집된 데이터를 기반으로 앞으로 생성될 값 예측

6.3.5 서머리와 히스토그램

서머리와 히스토그램은 모두 구간 내 특정 메트릭 값이 나타나는 빈도를 알려준다. 하지만 약간 차이가 있는데 서머리는 익스포터에서 이미 만들어진 값을 보여주고 히스토그램은 요청을 받으면 이를 계산해서 보여준다.

서머리

서머리는 이미 공개된 메트릭 값을 조회하면 바로 확인할 수 있다. prometheus_target_interval_length_seconds을 입력하면 결과에서 quantile(분위수)의 값에 매핑되는 메트릭 값을 확인한다. (quantile="0.99")는 100개 중 99번째 응답 시간을 의미한다. 이와 같은 100개 중 99번째 수를 99백분위수라고 한다.

히스토그램

히스토그램에 연산자 추가하기

프로메테우스의 그래프 기능이 제한적이므로 그라파나를 알아보자.

6.4 그라파나로 모니터링 데이터 시각화하기

헬름으로 그라파나 설치하기

$ ~/_Book_k8sInfra/ch6/6.4.1/grafana-preconfig.sh
[Step 1/4] Task [Check helm status]
[Step 1/4] ok
[Step 2/4] Task [Check MetalLB status]
[Step 2/4] ok
[Step 3/4] Task [Create NFS directory for grafana]
/nfs_shared/grafana created
[Step 3/4] Successfully completed
[Step 4/4] Task [Create PV,PVC for grafana]
persistentvolume/grafana created
persistentvolumeclaim/grafana created
[Step 4/4] Successfully completed
$ ~/_Book_k8sInfra/ch6/6.4.1/grafana-install.sh 
NAME: grafana
LAST DEPLOYED: Mon Feb 20 18:01:39 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace default grafana -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana.default.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:
NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running 'kubectl get svc --namespace default -w grafana'
     export SERVICE_IP=$(kubectl get svc --namespace default grafana -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
     http://$SERVICE_IP:80

3. Login with the password from step 1 and the username: admin
#!/usr/bin/env bash  
$ helm install grafana edu/grafana \  
--set persistence.enabled=true \  
--set persistence.existingClaim=grafana \  
--set service.type=LoadBalancer \  
--set securityContext.runAsUser=1000 \  
--set securityContext.runAsGroup=1000 \  
--set adminPassword="admin"
$ kubectl get svc grafana
NAME      TYPE           CLUSTER-IP       EXTERNAL-IP    PORT(S)        AGE
grafana   LoadBalancer   10.101.228.146   192.168.1.12   80:32733/TCP   109s

프로메테우스를 데이터 소스로 구성하기

노드 메트릭 데이터 시각화하기

클러스터 메트릭에서 노드 사용률

100 * (1 - node_filesystem_avail_bytes / node_filesystem_size_bytes) by (kubernetes_node) 노드 CPU 사용률

  • 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (kubernetes_node)
    • 1에서 메트릭 값을 빼는 이유는 유휴 상태 외에는 모두 사용하는 상태이므로 이를 제외한 총 CPU 사용률을 구하기 위해서다.
  • 시프트 + 엔터
  • 제목: 노드 CPU 사용률
  • 범례(Legend) : ``
    • 메트릭 레이블의 값을 사용해 시각화 대상을 쉽게 인지할 수 있게 한다.
  • Axes(축)
    • Y축(Left Y)의 단위(Unit)를 Misc > percent(0.0-1.0)으로 선택
  • Apply 클릭

노드 메모리 사용량

  • 메트릭: node_memory_Active_bytes
  • 범례: ``
  • Y축 단위: Data (Metric) -> bytes(Metric)
  • Apply 클릭

((node_filesystem_size_bytes - node_filesystem_free_bytes) / node_filesystem_size_bytes) * 100

노드 네트워크 평균 송신/수신 트래픽

  • 송신
    • 메트릭: avg(rate(node_network_transmit_bytes_total[5m])) by (kubernetes_node)
    • 범례: -transmit
  • Query 클릭
  • 수신
    • 메트릭: avg(rate(node_network_receive_bytes_total[5m])) by (kubernetes_node) * -1
      • 송신과 수신이 같은 단위로 표시되면 구분이 어려우니 -1을 곱함
    • 범례: -transmit
    • Y축 단위(Axes): Data (Metric) -> bytes(Metric)
  • Apply 클릭

노드 상태

  • 메트릭: up{job="kubernetes-nodes"}
  • 범례: ``
  • visualization
    • Graph -> Stat(값)
  • display
    • Value: Last (not null)
    • Orientation: Horizontal
    • Graph mode: None
  • filed 탭
    • value mappings
      • Add value mappings 클릭
        • Value: 1, Text: Good
        • Value: 0, Text: Bad
    • Thresholds
      • 위쪽 임곗값: Red -> Green으로 변경, 80 -> 1로 변경
      • 아랫쪽 임곗값: 색깔만 Red로 변경
  • Apply

Add new panel이 아니라 convert to row 클릭해서 토글을 하나 더 만든다.

6.4.4 파드 메트릭 데이터 시각화하기

파드는 여러 개의 네임스페이스에 존재하므로 사용자가 원하는 네임스페이스에 속한 파드만 확인할 수 있다면 편리할 것이다.

그라파나에서는 변수를 선언하고 선언한 변수로 사용자가 원하는 내용만을 선별해 대시보드에서 확인할 수 있다.

오른쪽 상단에 톱니바퀴 모양의 Dashboard settings(대시보드 설정) 클릭 왼쪽에 Variables, Add variable 클릭

  • Name: Namespace (대시보드에서 $Namespace로 변수를 사용할 수 있다)
  • Label: Namespace (대시보드에서 변수 선택 시 변수를 지칭하는 레이블)
  • Data Source: Prometheus (쿼리가 실행될 때 값을 받아오는 소스 설정)
  • Refresh: On Dashboard Load 대시보드가 로드될 때마다 새로 읽어들임 (변수를 읽어 들이는 방법 설정)
  • Query: label_values(kube_pod_info, namespace)에서 label_values는 프로메테우스 플러그인에서 제공하는 함수로 메트릭에 있는 특정 레이블의 값을 반환받을 수 있다. kube_pod_info의 네임스페이스 값을 그라파나의 네임스페이스 변수의 값으로 치환한다.
  • include All option: 모든 네임스페이스를 선택할 수 있는 옵션을 적용할지 말지를 결정한다. 스위치를 활성화하면 네임스페이스를 모두 선택할 수 있는 ALL 선택 옵션이 추가된다.
  • Custom all value: All 선택 옵션의 범위를 사용자가 지정할 수 있다. .+를 설정하면 하나 이상의 값을 가진 것들을 선택할 수 있다.

설정 항목을 모두 작성하면 변수로 사용할 수 있는 값이 Preview of values 항목 밑에 모두 표시된다. Add 버튼을 눌러 변수 설정을 완료한다.

오른쪽위에 New 버튼을 눌러 네임스페이스 하위에 속한 파드를 변수로 추가한다.

  • Name: Pod
  • Label: Pod
  • Data Source: prometheus
  • Refresh: On Dashboard Load
  • Query: label_values(kube_pod_info{namespace=~"$Namespace"}, pod) (네임스페이스에 속한 파드만 검색할 수 있다)
  • include All option: 파드 변수도 스위치를 활성화한다.
  • Custom all value: .+

$Pod Pod CPU 사용률

  • 메트릭: sum(rate(container_cpu_usage_seconds_total{namespace=~"$Namespace", pod=~"$Pod", container!=""}[5m])) by (pod)
  • 범례: ``
  • Y축 단위: Misc > percent(0.0-1.0)

$Pod Pod 메모리 사용량

  • 메트릭: sum(container_memory_usage_bytes{namespace=~"$Namespace", pod=~"$Pod", container!=""}) by (pod)
  • 범례: ``
  • Y축 단위: Data (Metric) -> bytes(Metric)

API 서버 응답시간(5분/SLA 99%)

  • 메트릭: histogram_quantile(0.99, sum(rate(apiserver_request_duration_seconds_bucket[5m])) by (le))
  • 시각화: Stat

Pod 상태

  • sum(kube_pod_status_phase{pod=~"$Pod", namespace=~"$Namespace"}) by (phase)
  • 범례: ``
  • 시각화: Stat

6.5 좀 더 견고한 모니터링 환경만들기

얼럿매니저로 이상 신호 감지하고 알려주기

그라파나의 얼럿 메뉴: 시각화 그래프에만 존재해서 제한적 프로메테우스의 얼럿 매니저

$ mkdir ~/webhook
$ cp ~/_Book_k8sInfra/ch6/6.5.1/alert-notifier.yaml ~/webhook/

# sed -i 's,Slack-URL,<복사한 URL>,g' \~/webhook/alert-notifier.yaml
$ sed -i 's,Slack-URL,https://hooks.slack.com/services/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,g' ~/webhook/alert-notifier.yaml
$ kubectl apply -f ~/webhook/alert-notifier.yaml
configmap/prometheus-notifier-config created

$ kubectl get configmap prometheus-notifier-config
NAME                         DATA   AGE
prometheus-notifier-config   1      29s
$ ~/_Book_k8sInfra/ch6/6.5.1/prometheus-alertmanager-preconfig.sh
[Step 1/4] Task [Check helm status]
[Step 1/4] ok
[Step 2/4] Task [Check MetalLB status]
[Step 2/4] ok
[Step 3/4] Task [Create NFS directory for alertmanager]
[Step 3/4] Successfully completed
[Step 4/4] Task [Create PV,PVC for alertmanager]
persistentvolume/prometheus-alertmanager created
persistentvolumeclaim/prometheus-alertmanager created
[Step 4/4] Successfully completed

$ ~/_Book_k8sInfra/ch6/6.5.1/prometheus-alertmanager-install.sh 
Release "prometheus" has been upgraded. Happy Helming!
NAME: prometheus
LAST DEPLOYED: Wed Feb 22 14:44:32 2023
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-server.default.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running 'kubectl get svc --namespace default -w prometheus-server'

  export SERVICE_IP=$(kubectl get svc --namespace default prometheus-server -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:80


The Prometheus alertmanager can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-alertmanager.default.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  NOTE: It may take a few minutes for the LoadBalancer IP to be available.
        You can watch the status of by running 'kubectl get svc --namespace default -w prometheus-alertmanager'

  export SERVICE_IP=$(kubectl get svc --namespace default prometheus-alertmanager -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
  echo http://$SERVICE_IP:80
#################################################################################
######   WARNING: Pod Security Policy has been moved to a global property.  #####
######            use .Values.podSecurityPolicy.enabled with pod-based      #####
######            annotations                                               #####
######            (e.g. .Values.nodeExporter.podSecurityPolicy.annotations) #####
#################################################################################



For more information on running Prometheus, visit:
https://prometheus.io/

부록

A.1 kubectl 명령 자동 완성하기

kubectl을 자동 완성형으로 쓰기 위해서는 kubectl completion bash로 나온 결과를 설정(/etc/bash_completion.d/kubectl)에 저장하고 이를 접속 시에 배시 셸 설정(~/.bashrc)에서 불러오도록 하면 된다.

bash-completion.sh

#!/usr/bin/env bash  
#Usage: #1. bash <(curl -s https://raw.githubusercontent.com/sysnet4admin/IaC/master/manifests/bash-completion.sh)   
# install bash-completion for kubectl yum install bash-completion -y   
  
# kubectl completion on bash-completion dir  
kubectl completion bash >/etc/bash_completion.d/kubectl  
  
# alias kubectl to k echo 'alias k=kubectl' >> ~/.bashrc  
echo 'complete -F __start_kubectl k' >> ~/.bashrc  
  
#Reload rc  
su -

kubectl 약어 확인

$ kubectl api-resources
NAME                              SHORTNAMES   APIGROUP                       NAMESPACED   KIND
bindings                                                                      true         Binding
componentstatuses                 cs                                          false        ComponentStatus
configmaps                        cm                                          true         ConfigMap
endpoints                         ep                                          true         Endpoints
events                            ev                                          true         Event
limitranges                       limits                                      true         LimitRange
namespaces                        ns                                          false        Namespace
nodes                             no                                          false        Node
persistentvolumeclaims            pvc                                         true         PersistentVolumeClaim
persistentvolumes                 pv                                          false        PersistentVolume
pods                              po                                          true         Pod
podtemplates                                                                  true         PodTemplate
replicationcontrollers            rc                                          true         ReplicationController
resourcequotas                    quota                                       true         ResourceQuota
secrets                                                                       true         Secret
serviceaccounts                   sa                                          true         ServiceAccount
services                          svc                                         true         Service
mutatingwebhookconfigurations                  admissionregistration.k8s.io   false        MutatingWebhookConfiguration
validatingwebhookconfigurations                admissionregistration.k8s.io   false        ValidatingWebhookConfiguration
customresourcedefinitions         crd,crds     apiextensions.k8s.io           false        CustomResourceDefinition
apiservices                                    apiregistration.k8s.io         false        APIService
controllerrevisions                            apps                           true         ControllerRevision
daemonsets                        ds           apps                           true         DaemonSet
deployments                       deploy       apps                           true         Deployment
replicasets                       rs           apps                           true         ReplicaSet
statefulsets                      sts          apps                           true         StatefulSet
tokenreviews                                   authentication.k8s.io          false        TokenReview
localsubjectaccessreviews                      authorization.k8s.io           true         LocalSubjectAccessReview
selfsubjectaccessreviews                       authorization.k8s.io           false        SelfSubjectAccessReview
selfsubjectrulesreviews                        authorization.k8s.io           false        SelfSubjectRulesReview
subjectaccessreviews                           authorization.k8s.io           false        SubjectAccessReview
horizontalpodautoscalers          hpa          autoscaling                    true         HorizontalPodAutoscaler
cronjobs                          cj           batch                          true         CronJob
jobs                                           batch                          true         Job
certificatesigningrequests        csr          certificates.k8s.io            false        CertificateSigningRequest
leases                                         coordination.k8s.io            true         Lease
bgpconfigurations                              crd.projectcalico.org          false        BGPConfiguration
bgppeers                                       crd.projectcalico.org          false        BGPPeer
blockaffinities                                crd.projectcalico.org          false        BlockAffinity
clusterinformations                            crd.projectcalico.org          false        ClusterInformation
felixconfigurations                            crd.projectcalico.org          false        FelixConfiguration
globalnetworkpolicies                          crd.projectcalico.org          false        GlobalNetworkPolicy
globalnetworksets                              crd.projectcalico.org          false        GlobalNetworkSet
hostendpoints                                  crd.projectcalico.org          false        HostEndpoint
ipamblocks                                     crd.projectcalico.org          false        IPAMBlock
ipamconfigs                                    crd.projectcalico.org          false        IPAMConfig
ipamhandles                                    crd.projectcalico.org          false        IPAMHandle
ippools                                        crd.projectcalico.org          false        IPPool
networkpolicies                                crd.projectcalico.org          true         NetworkPolicy
networksets                                    crd.projectcalico.org          true         NetworkSet
endpointslices                                 discovery.k8s.io               true         EndpointSlice
events                            ev           events.k8s.io                  true         Event
ingresses                         ing          extensions                     true         Ingress
ingressclasses                                 networking.k8s.io              false        IngressClass
ingresses                         ing          networking.k8s.io              true         Ingress
networkpolicies                   netpol       networking.k8s.io              true         NetworkPolicy
runtimeclasses                                 node.k8s.io                    false        RuntimeClass
poddisruptionbudgets              pdb          policy                         true         PodDisruptionBudget
podsecuritypolicies               psp          policy                         false        PodSecurityPolicy
clusterrolebindings                            rbac.authorization.k8s.io      false        ClusterRoleBinding
clusterroles                                   rbac.authorization.k8s.io      false        ClusterRole
rolebindings                                   rbac.authorization.k8s.io      true         RoleBinding
roles                                          rbac.authorization.k8s.io      true         Role
priorityclasses                   pc           scheduling.k8s.io              false        PriorityClass
csidrivers                                     storage.k8s.io                 false        CSIDriver
csinodes                                       storage.k8s.io                 false        CSINode
storageclasses                    sc           storage.k8s.io                 false        StorageClass
volumeattachments                              storage.k8s.io                 false        VolumeAttachment

B 쿠버 대시보드 구성하기

$ kubectl apply -f ~/_Book_k8sInfra/app/B.kube-dashboard/dashboard.yaml
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created

D.1 쿠버네티스가 컨테이너를 다루는 과정

1) 사용자는 kube-apiserver의 URL로 요청을 전달하거나 kubectl 명령어로 kube-apiserver에 파드를 생성하는 명령을 내린다.

2) 파드를 생성하는 명령은 네트워크를 통해 kubelet으로 전달된다. kube-apiserver는 노드에 있는 kubelet과 안전하게 통신하기 위해 인증서와 키로 통신 내용을 암호화해 전달한다.

이때 키는 마스터 노드의 etc/kubernetes/pki 디렉터리에 보관돼 있고, 인증서 파일인 api-server-kubelet-client.crt와 키파일인 apiserver-kubelet-client.key를 사용한다.

3) kubelet에서 요청을 검증하고 나면 컨테이너디에 컨테이너 생성 명령을 내린다. 이때 명령 형식은 컨테이너 런타임 인터페이스(CRI) 규약을 따른다.

CRI는 컨테이너와 관련된 명령을 내리는 런타임 서비스와 이미지와 관련된 명령을 내리는 이미지 서비스로 이뤄져 있다. 런타임 서비스는 파드의 생성, 삭제, 정지 그리고 컨테이너의 생성, 삭제 등의 다양한 명령을 내린다.

kubelet이 내린 명령은 컨테이너디에 통합된 CRI 플러그인이라는 구성 요소에 전달된다. CRI 플러그인은 컨테이너디에 통합돼 있으므로 컨테이너디가 컨테이너를 생성하는 명령을 직접 호출한다.

4) 컨테이너디는 containered-shim이라는 자식 프로세스를 생성해 컨테이너를 관리한다.

$ ps -ef | head -n 1 && ps -ef | grep -v auto | grep containerd
UID        PID  PPID  C STIME TTY          TIME CMD
root       992     1  0 10:20 ?        00:01:09 /usr/bin/containerd
root       996     1  1 10:20 ?        00:07:26 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
root      2713   992  0 10:20 ?        00:00:01 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/3bb68549701618cfaccf217b47b3bd966c6e555eb72e681d70d1e7f2eb1184ec -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root      2967   992  0 10:20 ?        00:00:01 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/d22fa305396cd8f770dfc0c8d061623b288b09aa5c1ac1adfed3b325dab2d7de -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
root      3014   992  0 10:20 ?        00:00:01 containerd-shim -namespace moby -workdir /var/lib/containerd/io.containerd.runtime.v1.linux/moby/c0e58036650883e5f0ad63476e7d11a5db901c2d75d5cc6bf06694420b59452c -address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd -runtime-root /var/run/docker/runtime-runc
...

5) containered-shim 프로세스는 컨테이너를 조작한다. 실제로 containered-shim이 runC 바이너리 실행 파일을 호출해 컨테이너를 생성한다.

D.2 컨테이너 PID 1의 의미

$ ps -ef | more
 ps -ef | more
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 10:20 ?        00:00:28 /usr/lib/systemd/systemd --switched-root --system --deserialize 22
root         2     0  0 10:20 ?        00:00:00 [kthreadd]
root         4     2  0 10:20 ?        00:00:00 [kworker/0:0H]
root         6     2  0 10:20 ?        00:00:27 [ksoftirqd/0]
root         7     2  0 10:20 ?        00:00:02 [migration/0]
root         8     2  0 10:20 ?        00:00:00 [rcu_bh]
root         9     2  0 10:20 ?        00:01:23 [rcu_sched]
root        10     2  0 10:20 ?        00:00:00 [lru-add-drain]
root        11     2  0 10:20 ?        00:00:00 [watchdog/0]
root        12     2  0 10:20 ?        00:00:00 [watchdog/1]
root        13     2  0 10:20 ?        00:00:01 [migration/1]
root        14     2  0 10:20 ?        00:00:57 [ksoftirqd/1]
root        16     2  0 10:20 ?        00:00:00 [kworker/1:0H]
root        18     2  0 10:20 ?        00:00:00 [kdevtmpfs]
root        19     2  0 10:20 ?        00:00:00 [netns]
...
$ docker run -d nginx
Unable to find image 'nginx:latest' locally
latest: Pulling from library/nginx
bb263680fed1: Pull complete 
258f176fd226: Pull complete 
a0bc35e70773: Pull complete 
077b9569ff86: Pull complete 
3082a16f3b61: Pull complete 
7e9b29976cce: Pull complete 
Digest: sha256:6650513efd1d27c1f8a5351cbd33edf85cc7e0d9d0fcb4ffb23d8fa89b601ba8
Status: Downloaded newer image for nginx:latest
a006c21dd29895539562f33e8ef24dd56a84a6c35f92d2af8328bab4a00c5940

$ ps -ef | grep -v auto | grep nginx
root     25827 25810  0 17:43 ?        00:00:00 nginx: master process nginx -g daemon off;
101      25886 25827  0 17:43 ?        00:00:00 nginx: worker process
101      25887 25827  0 17:43 ?        00:00:00 nginx: worker process
$ docker exec a006 ls -l /proc/1/exe
lrwxrwxrwx. 1 root root 0 Feb 22 08:45 /proc/1/exe -> /usr/sbin/nginx

Cgroup Namespace

D.3 도커 아닌 runC로 컨테이너 생성하기

runC는 오픈 컨테이너 이니셔티브에서 만든 컨테이너 생성 및 관리를 위한 표준 규격이다. 컨테이너디, 크라이오 등의 컨테이너 런타임이 내부적으로 runC를 활용해 표준 규격을 따른다.

컨테이너디나 크라이오 같은 요소를 고수준 컨테이너 런타임, 실제로 컨테이너를 조작하기 위해 리눅스에 명령을 내리는 runC를 저수준 컨테이너 런타임으로 분류한다.

$ docker run -d nginx
b5eb6fd1ad9030d72ec25c4937c5df9b07b86d70bc5c2d99059d7d96e908cb91

$ docker export b5eb > nginx.tar

$ ls nginx.tar
nginx.tar
$ mkdir nginx-container

$ tar -C nginx-container -xvf nginx.tar

$ ls nginx-container
bin   dev                  docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc                   lib   media  opt  root  sbin  sys  usr
$ ~/_Book_k8sInfra/app/D.DeepDiveContainer/ns-create.sh 
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.kakao.com
 * epel: mirror-jp.misakamikoto.network
 * extras: mirror.kakao.com
 * updates: mirror.kakao.com
Resolving Dependencies
--> Running transaction check
---> Package bridge-utils.x86_64 0:1.5-9.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

===============================================================================================================================
 Package                           Arch                        Version                         Repository                 Size
===============================================================================================================================
Installing:
 bridge-utils                      x86_64                      1.5-9.el7                       base                       32 k

Transaction Summary
===============================================================================================================================
Install  1 Package

Total download size: 32 k
Installed size: 56 k
Downloading packages:
bridge-utils-1.5-9.el7.x86_64.rpm                                                                       |  32 kB  00:00:00     
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Installing : bridge-utils-1.5-9.el7.x86_64                                                                               1/1 
  Verifying  : bridge-utils-1.5-9.el7.x86_64                                                                               1/1 

Installed:
  bridge-utils.x86_64 0:1.5-9.el7                                                                                              

Complete!
$ ip addr list nginx
15: nginx: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 76:6b:32:a1:80:e6 brd ff:ff:ff:ff:ff:ff
    inet 192.168.200.1/24 scope global nginx
       valid_lft forever preferred_lft forever
$ cp ~/_Book_k8sInfra/app/D.DeepDiveContainer/config.json .

$  runc run nginx-container
# ls
bin   dev                  docker-entrypoint.sh  home  lib64  mnt  proc  run   srv  tmp  var
boot  docker-entrypoint.d  etc                   lib   media  opt  root  sbin  sys  usr
# ./docker-entrypoint.sh nginx
./docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
./docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
./docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
10-listen-on-ipv6-by-default.sh: info: IPv6 listen already enabled
./docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
./docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
./docker-entrypoint.sh: Configuration complete; ready for start up
2023/02/22 09:58:02 [notice] 7#7: using the "epoll" event method
2023/02/22 09:58:02 [notice] 7#7: nginx/1.23.3
2023/02/22 09:58:02 [notice] 7#7: built by gcc 10.2.1 20210110 (Debian 10.2.1-6) 
2023/02/22 09:58:02 [notice] 7#7: OS: Linux 3.10.0-1127.19.1.el7.x86_64
2023/02/22 09:58:02 [notice] 7#7: getrlimit(RLIMIT_NOFILE): 1024:1024
2023/02/22 09:58:02 [notice] 23#23: start worker processes
2023/02/22 09:58:02 [notice] 23#23: start worker process 24
2023/02/22 09:58:02 [notice] 23#23: start worker process 25
$ curl 192.168.200.2
$ ps -ef | grep -v auto | grep nginx
root     25827 25810  0 17:43 ?        00:00:00 nginx: master process nginx -g daemon off;
101      25886 25827  0 17:43 ?        00:00:00 nginx: worker process
101      25887 25827  0 17:43 ?        00:00:00 nginx: worker process
root     32475 32458  0 18:46 ?        00:00:00 nginx: master process nginx -g daemon off;
101      32549 32475  0 18:46 ?        00:00:00 nginx: worker process
101      32550 32475  0 18:46 ?        00:00:00 nginx: worker process

~/_Book_k8sInfra/app/D.DeepDiveContainer/ns-remover.sh 
Loaded plugins: fastestmirror
Resolving Dependencies
--> Running transaction check
---> Package bridge-utils.x86_64 0:1.5-9.el7 will be erased
--> Finished Dependency Resolution

Dependencies Resolved

===============================================================================================================================
 Package                           Arch                        Version                        Repository                  Size
===============================================================================================================================
Removing:
 bridge-utils                      x86_64                      1.5-9.el7                      @base                       56 k

Transaction Summary
===============================================================================================================================
Remove  1 Package

Installed size: 56 k
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  Erasing    : bridge-utils-1.5-9.el7.x86_64                                                                               1/1 
  Verifying  : bridge-utils-1.5-9.el7.x86_64                                                                               1/1 

Removed:
  bridge-utils.x86_64 0:1.5-9.el7                                                                                              

Complete!

Source