컨테이너 인프라 환경 구축을 위한 쿠버네티스 도커 2
- 5장 지속적 통합과 배포 자동화, 젠킨스
- 6장 안정적인 운영을 완성하는 모니터링, 프로메테우스와 그라파나
- 부록
- Source
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)
으로 선택
- Y축(Left Y)의 단위(Unit)를
- 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
- Add value mappings 클릭
- Thresholds
- 위쪽 임곗값: Red -> Green으로 변경, 80 -> 1로 변경
- 아랫쪽 임곗값: 색깔만 Red로 변경
- value mappings
- 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!