🎈 END to END 암호화
HTTPS Nginx 기본 방식 (5/24)
♢ 단점
각 파드마다
공격 트래픽도 암호화되어 전달 ➜ 각 파드마다 별도의 보안 어플리케이션 파드 필요 ➜ 부하 증가 / 보안 장비 관리 어려움
평문 통신
♢ 장점
🎈 TLS Termination 실습
ing.spec.tls
♢ 구조도
ingress-tls-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: ingress-tls-secret
type: kubernetes.io/tls
data:
# base64 x509/nginx-tls.crt -w 0
tls.crt: |
LS0tLS1CRUd...
# base64 x509/nginx-tls.key -w 0
tls.key: |
LS0tLS1CRUdJ...
myweb-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myweb-rs
spec:
replicas: 3
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
myweb-svc-np.yaml
apiVersion: v1
kind: Service
metadata:
name: myweb-svc-np
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 8080
myweb-ingress-tls.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myweb-ing-tls
spec:
tls: ✔️
- hosts:
- '*.nip.io'
secretName: ingress-tls-secret
rules:
- host: '*.nip.io'
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myweb-svc-np
port:
number: 80
생성
$ kubectl create -f myweb-rs.yaml
$ kubectl create -f myweb-svc-np.yaml
$ kubectl create -f ingress-tls-secret.yaml
$ kubectl create -f myweb-ingress-tls.yaml
or
$ kubectl create -f .
확인
kubectl get svc,ing,rs,po
$ curl -k https://192-168-100-100.nip.io
$ curl -k https://192-168-100-100.nip.io -v # 자세한 결과
➜ TLS : 4way handshake
🌟 파드의 순서
와 고유성(이름)
보장
- 애완동물(Stateless) ➜ 죽으면 대체할 방법 x, 병 걸리면 치료
(고유성
존재 - 자신만의 데이터를 보유 ex. DB 등 포함)- 가축(Stateful) ➜ 죽어도
교체
(대체) 가능
Stateless, immutable
: 상태가 없는, 불변
ex. 웹서비스, RS, DS, RC, Deploy
불변의 이미지로 컨테이너를 만들고 컨테이너 장애 발생 시 컨테이너 교체
하는 형태
Stateful
: 상태가 있는
다운되면 안되는 필수 or 교유한 정보를 포함한 시스템 서버 (다운가능성 존재)
ex. RDBMS : 관계형 데이터베이스, STS(StatefulSet, 과거 PetSets)
보통 Master(R/W) / Slave(Read only) 구조로 구성
클러스터 네트워크 내부에서 서비스에 속한 Pod에 직접 접근 가능한 고유 IP와 DNS 생성
로드밸런싱이 필요없거나 단일 서비스 IP가 필요 없는 경우
작업 디렉토리 : ~/yaml/cont/sts/headless
myweb-rs.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myweb-rs
spec:
replicas: 3
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
myweb-svc.yaml
Cluster IP type 예시
apiVersion: v1
kind: Service
metadata:
name: myweb-svc
spec:
type: ClusterIP ✔️
selector:
app: web
ports:
- port: 80
targetPort: 8080
myweb-svc-headless.yaml
Headless Service
svc.spec.clusterIP
None
으로 설정 == 서비스의 IP 자체가 없는 것파드의 갯수
만큼 응답apiVersion: v1
kind: Service
metadata:
name: myweb-svc-headless
spec:
type: ClusterIP ✔️
clusterIP: None ✔️ # Headless Service
selector:
app: web
ports:
- port: 80
targetPort: 8080
Client 생성해서 확인
$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
# host myweb-svc
# host myweb-svc-headless
> host myweb-svc
myweb-svc.default.svc.cluster.local has address 10.233.17.120
> host myweb-svc-headless
myweb-svc-headless.default.svc.cluster.local has address 10.233.92.74
myweb-svc-headless.default.svc.cluster.local has address 10.233.90.45
myweb-svc-headless.default.svc.cluster.local has address 10.233.96.93
💡 Deployment/RS vs STS
♢ Deployment/RS
모든 파드가 같은 볼륨
에 마운팅
파드가 가지고 있는 데이터 모두 동일 ➜ 고유성 x
♢ STS
각 파드마다 별도의 볼륨
보유 ➜ 고유성 o
ex. DB 파드
Master와 Slave를 구별하여 Data Write
➜ 🌟 statefulset + headless 서비스
같이 사용 (필수)
$ kubectl api-resources | grep statefulsets
statefulsets sts apps/v1 true StatefulSet
sts.spec.serviceName
sts.spec.updateStrategy
확인
작업 디렉토리 : ~/yaml/cont/sts/sts1
myweb-sts.yaml
apiVersion: apps/v1
kind: StatefulSet ✔️
metadata:
name: myweb-sts
spec:
replicas: 3
serviceName: myweb-svc-headless
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
protocol: TCP
myweb-svc-headless.yaml
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: myweb-rs
spec:
replicas: 3
apiVersion: v1
kind: Service
metadata:
name: myweb-svc
spec:
apiVersion: v1
kind: Service
metadata:
name: myweb-svc-headless
spec:
type: ClusterIP ✔️
clusterIP: None ✔️ # Headless Service
selector:
app: web
ports:
- port: 80
targetPort: 8080
🎈 scaling
$ kubectl scale myweb-sts --relicas 4 # 증가 ➜ 3번 생성
$ kubectl scale myweb-sts --relicas 2 # 감소 ➜ 3번 삭제 후 2번 삭제
$ kubectl scale myweb-sts --relicas 4 # 2번 생성 후 3번 생성
생성 / 삭제
에도 순서
존재
🎈 Test 파드 생성해서 ip 호스팅하기
$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
sts + headless svc
고정적
으로 파드 이름
생성
Staless 파드는 삭제되면 다른 이름을 가진 파드 생성
🌟 sts
는 파드를 삭제해도 동일 Node
에 동일 ID
로 동일한 이름
으로 재생성
replicas 순서에 따라 이름 뒤에 서수
(0,1,2,...) 붙음
파드 이름으로 쿼리 전송 가능
➜ 파드 특정
가능
> host myweb-sts-0.myweb-svc-headless
> host myweb-sts-1.myweb-svc-headless
> host myweb-sts-2.myweb-svc-headless
> host myweb-sts-3.myweb-svc-headless
$ host
: 해당 파드의 ip address 정보 출력
🎈 STS 제한사항
pvc
형태만 사용 가능 -> StorageClass
사용해 프로비저닝작업 디렉토리 : ~/yaml/cont/sts/sts2
myweb-sts-vol.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: myweb-sts-vol
spec:
replicas: 3
serviceName: myweb-svc-headless
selector:
matchLabels:
app: web
env: dev
template:
metadata:
labels:
app: web
env: dev
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb:alpine
ports:
- containerPort: 8080
protocol: TCP
volumeMounts:
- name: myweb-pvc
mountPath: /data
volumeClaimTemplates: ✔️
- metadata:
name: myweb-pvc
spec:
accessModes:
- ReadWriteOnce ✔️
resources:
requests:
storage: 1G
storageClassName: nfs-client
🎈 STS spec 구성요소
template
: pod를 생성하기 위한 탬플릿volumeClaimTemplates
: pvc 생성을 위한 템플릿volumeClaimTemplates.spec
: pvc 설정 탬플릿생성 확인
$ kubectl get sts,po,pv,pvc
NAME READY AGE
statefulset.apps/myweb-sts-vol 1/3 6s
NAME READY STATUS RESTARTS AGE
pod/myweb-sts-vol-0 ✔️ 1/1 Running 0 6s
pod/myweb-sts-vol-1 ✔️ 0/1 Pending 0 1s
pod/nfs-client-provisioner-758f8cd4d6-svrqw 1/1 Running 0 15h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
✔️persistentvolume/pvc-3b0546a7-a439-4b60-b552-3f0b562f87ca 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-0 nfs-client 5s
✔️persistentvolume/pvc-969eb7dd-e3be-4084-909a-cc660892fdc2 1G RWO Delete Bound default/myweb-pvc-myweb-sts-vol-1 nfs-client 0s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
✔️persistentvolumeclaim/myweb-pvc-myweb-sts-vol-0 Bound pvc-3b0546a7-a439-4b60-b552-3f0b562f87ca 1G RWO nfs-client 6s
✔️persistentvolumeclaim/myweb-pvc-myweb-sts-vol-1 Bound pvc-969eb7dd-e3be-4084-909a-cc660892fdc2 1G RWO nfs-client 1s
pv 개수 == pvc 개수
파드를 delete해도 pv, pvc는 없어지지 않고 파드만 삭제
파드 scaling 하면 해당 이름에 바운딩 되어있던 pvc-pv에 다시 연결
볼륨 고유성 확인
$ kubectl exec myweb-sts-vol-0 -it -- sh
>cd /data
>ls
>touch a b c
>ls
>>a b c
>exit
$ kubectl exec myweb-sts-vol-1 -it -- sh
>cd /data
>ls
# a b c 없음
>touch x y z
>ls
>>x y z
파드 지웠다가 연결해도 원래 pvc, pv에 그대로 연결하기 때문에 동일 데이터 출력
$ kubectl delete po myweb-sts-vol-1 # 삭제
$ kubectl get po # 삭제 확인
# 삭제 된 파드와 동일한 이름의 파드 자동 재생성
$ kubectl exec myweb-sts-vol-1 -it -- sh # 연결
>cd /data
>ls
>>x y z # ➜ 기존 볼륨의 데이터 그대로 존재
🎈 구조
One Master-Multi Slave
➜ RW 가능한 Master DB + 나머지 RO Slave DB
백업을 다른 디스크가 아니라 다른 DB
에 하는 형태
클라이언트는 기본적으로 마스터만 사용
Master가 죽으면 Slave DB를 Master로 승격하여 접속 가능
💡 왜 RS / Deploy를 사용하지 않을까?
RDBMS는 같은 볼륨을 사용하는 구조가 기본적으로 불가함
🎈 생성 과정
실습 과정 상세 기술 페이지 ➜ 들어가서 yaml 파일 내용 확인
작업 디렉토리 : ~/yaml/cont/sts/sts3
파일 다운 받은 후 수정
STS yaml 파일 다운
$ wget https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
mysql-statefulset.yaml
...
80 requests:
81 cpu: 300m
82 memory: 300M
...
167 storageClassName: nfs-client # 이전에 생성해놓았던 sc
Service yaml 파일 다운
$ kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
mysql-services.yaml
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
서비스 2개(일반/headless) 만들고 같은 레이블 사용 ➜ 같은 파드 가리킴
마스터-슬레이브 구분 위해 headless 서비스 사용
ConfigMap yaml 파일 다운
$ wget https://k8s.io/examples/application/mysql/mysql-configmap.yaml
mysql-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
data:
primary.cnf: |
# Apply this config only on the primary.
[mysqld]
log-bin
# datadir=/var/lib/mysql
replica.cnf: |
# Apply this config only on replicas.
[mysqld]
super-read-only
# datadir=/var/lib/mysql
✔️ mysql-configmap.yaml
에서 datadir=/var/lib/mysql/mysql
부분 둘 다 삭제
ConfigMap, sts, svc 생성
$ kubectl create -f mysql-configmap.yaml
$ kubectl create -f mysql-statefulset.yaml
생성 확인
$ kubectl get sts,po,pv,pvc,svc,ep,cm
NAME READY AGE
statefulset.apps/mysql 3/3 8m12s
NAME READY STATUS RESTARTS AGE
pod/mysql-0 2/2 Running 0 8m11s
pod/mysql-1 2/2 Running 1 (3m40s ago) 5m53s
pod/mysql-2 2/2 Running 0 3m35s
pod/nfs-client-provisioner-758f8cd4d6-svrqw 1/1 Running 0 16h
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-7088e9d6-85e2-4689-86ec-7b58e40a5b07 10Gi RWO Delete Bound default/data-mysql-0 nfs-client 8m11s
persistentvolume/pvc-9707b15f-a3e1-48ca-be82-c65e039d6ca9 10Gi RWO Delete Bound default/data-mysql-2 nfs-client 3m35s
persistentvolume/pvc-ef570914-bf39-49cc-b847-7dcf538ceebc 10Gi RWO Delete Bound default/data-mysql-1 nfs-client 5m53s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mysql-0 Bound pvc-7088e9d6-85e2-4689-86ec-7b58e40a5b07 10Gi RWO nfs-client 8m12s
persistentvolumeclaim/data-mysql-1 Bound pvc-ef570914-bf39-49cc-b847-7dcf538ceebc 10Gi RWO nfs-client 5m53s
persistentvolumeclaim/data-mysql-2 Bound pvc-9707b15f-a3e1-48ca-be82-c65e039d6ca9 10Gi RWO nfs-client 3m35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.233.0.1 <none> 443/TCP 3d1h
service/mysql ClusterIP None <none> 3306/TCP 8m12s
service/mysql-read ClusterIP 10.233.8.236 <none> 3306/TCP 8m12s
service/nginx-svc-lb LoadBalancer 10.233.24.166 192.168.100.240 80:32404/TCP,443:31238/TCP 15h
NAME ENDPOINTS AGE
endpoints/k8s-sigs.io-nfs-subdir-external-provisioner <none> 16h
endpoints/kubernetes 192.168.100.100:6443 3d1h
endpoints/mysql 10.233.90.47:3306,10.233.92.81:3306,10.233.96.95:3306 8m12s
endpoints/mysql-read 10.233.90.47:3306,10.233.92.81:3306,10.233.96.95:3306 8m12s
endpoints/nginx-svc-lb <none> 15h
NAME DATA AGE
configmap/kube-root-ca.crt 1 3d1h
configmap/mysql 2 8m12s
configmap/nginx-tls-config 1 15h
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
storageclass.storage.k8s.io/nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 16h
🎈 자동 동기화 Test
Test용 nettool 생성 (Cluster IP type이기 때문)
kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm
1) host 출력 값 개수 다른 것 확인
> host mysql
mysql.default.svc.cluster.local has address 10.233.96.95
mysql.default.svc.cluster.local has address 10.233.92.81
mysql.default.svc.cluster.local has address 10.233.90.47
> host mysql-read
mysql-read.default.svc.cluster.local has address 10.233.8.236
2) 데이터베이스 생성 동기화 확인
# Master DB 접속
> mysql -h mysql-0.mysql -u root
# env로 MYSQL_ALLOW_EMPTY_PASSWORD를 지정해주었기 때문에 패스워드 없이 접속 가능 -> 이 것 때문에 보안에 취약하므로 test용으로만 사용할 것
> MySQL [(none)]> show databases;
> MySQL [(none)]> create database encore; # DB 생성
> MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| encore✔️ |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.007 sec)
>MySQL [(none)]> exit
# RO DB 접속
> mysql -h mysql-1.mysql -u root
> MySQL [(none)]> show databases;
# Master DB에서 만들었던 encore DB가 존재
+------------------------+
| Database |
+------------------------+
| information_schema |
| encore✔️ |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.009 sec)
> MySQL [(none)]> drop database encore;
# 읽기 전용이므로 drop 불가 ✔️
ERROR 1290 (HY000): The MySQL server is running with the --super-read-only option so it cannot execute this statement
> MySQL [(none)]> exit
# Master DB 접속
> mysql -h mysql-0.mysql -u root
> MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| encore✔️ |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
6 rows in set (0.054 sec)
> MySQL [(none)]> drop database encore; # ✔️ DB 삭제
Query OK, 0 rows affected (0.075 sec)
> MySQL [(none)]> show databases;
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.009 sec)
> MySQL [(none)]> exit
# RO DB 접속
> mysql -h mysql-1.mysql -u root
> MySQL [(none)]> show databases;
# encore DB가 삭제되어있음 ✔️
+------------------------+
| Database |
+------------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| xtrabackup_backupfiles |
+------------------------+
5 rows in set (0.006 sec)
> MySQL [(none)]> exit
3) 레코드 생성 후 동기화 확인
# Master DB 접속
> mysql -h mysql-0.mysql -u root
> MySQL [(none)]> create database encore;
Query OK, 1 row affected (0.016 sec)
> MySQL [(none)]> use encore;
Database changed
# message라는 테이블 생성
> MySQL [encore]> create table encore.message (message VARCHAR(50));
Query OK, 0 rows affected (0.110 sec)
> MySQL [encore]> show tables;
+------------------+
| Tables_in_encore |
+------------------+
| message |
+------------------+
1 row in set (0.009 sec)
# message 테이블에 내용 입력
> MySQL [encore]> insert into encore.message values ("hello mysql");
Query OK, 1 row affected (0.161 sec)
# message 테이블 내용 확인
> MySQL [encore]> select * from message;
+-------------+
| message |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.001 sec)
> MySQL [encore]> exit
# RO DB 접속
> mysql -h mysql-1.mysql -u root
> MySQL [(none)]> use encore
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
# 동기화 되어 테이블 생성되어있음 ✔️
> MySQL [encore]> show tables;
+------------------+
| Tables_in_encore |
+------------------+
| message |
+------------------+
1 row in set (0.006 sec)
> MySQL [encore]> select * from message;
+-------------+
| message |
+-------------+
| hello mysql |
+-------------+
1 row in set (0.002 sec)
💡 새로 만들어진 파드에도 레코드 정상적으로
동기화
되어 만들어지기 때문에 바로 쿼리 가능
4) 볼륨 마운트 위치 확인
작업 디렉토리 : ~
$ sudo -i
> cd /nfsvolume/
> ls
deault-data-mysql-0-pvc-xxx deault-data-mysql-1-pvc-xxx deault-data-mysql-2-pvc-xxx
> cd deault-data-mysql-0-pvc-xxx
> ls
mysql
> cd mysql
> ls ➜ 여기 내용이 /var/lib/mysql에 마운트
➕ mysql Replicas 스케일링
$ kubectl scale sts mysql --replicas 2
➕ sharding 기법을 쓰면 모든 데이터베이스를 동기화 할 필요는 x
아주 큰 사이즈의 DB 하나만 두고 계속 백업 (백업 필수)
➜ 가장 싸고 간단
리소스 관리(제한)
🎈 QoS(Quality of Service, 서비스 품질) Class
BestEffort
: 가장 나쁨Burstable
Guaranteed
: 가장 좋음리소스 할당 우선순위 : Guaranteed > Burstable >BestEffort
- 요청, 제한 설정 x : BestEffort
- 요청 < 제한 : Burstable
- 요청 == 제한 : Guaranteed (Best👍)
🎈 요청 및 제안 특성
요청
스케줄러는 kubelet이 요청한만큼 사용할 수 있도록 보장
➜ 다 사용하든 다 사용하지 않든 상관 x
➜ 요청 용량을 무조건 확보
해서 제공 🌟
제한
최대
로 늘려서 사용할 수 있는 용량
미리 확보 x
-> 다른 파드의 request 양에 따라 달라짐
pod.spec.containers.resources.requests
키-벨류 형태
pod.spec.containers.resources.limits
🎈 CPU 요청 & 제한
millicore 단위
ex. 1500m ➜ 1.5개, 1000m ➜ 1개
Memory 요청 & 제한 단위 : M, G, T, T, Mi, Gi, Ti
🎈 Request & Limit 실습
➕ request 만 생성하면 limit 는 자동 세팅되지 x
myweb-reqlim.yaml
apiVersion: v1
kind: Pod
metadata:
name: myweb-reqlit
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
resources:
requests: ✔️
cpu: 200m
memory: 200M
limits: ✔️
cpu: 200m
memory: 200M
파드 생성
$ kubectl create -f myweb-reqlim.yaml
request == limit
$ kubectl describe po myweb-reqlit
...
QoS Class: Guaranteed ✔️
...
request의 memory를 100m으로 수정
request < limit
$ kubectl replace -f myweb-reqlim.yaml --force
--force
옵션 : 수정 불가인 것을 삭제했다가 다시 만드는 것 (업데이트 x)
$ kubectl describe po myweb-reqlit
...
QoS Class: Burstable ✔️
...
pod.spec.containers.resources 필드 삭제
request > limit
$ kubectl replace -f myweb-reqlim.yaml --force
$ kubectl describe po myweb-reqlit
...
QoS Class: BestEffort ✔️
...
각 노드의 CPU / Memory 사용량 확인
$ kubectl top nodes
$ kubectl top nodes
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
node1 769m 42% 1701Mi 52% -> 커널이 사용하는 양 빼고 생각
node2 385m 20% 934Mi 36%
node3 412m 21% 1117Mi 43%
각 Node에 떠있는 파드 개수 확인
$ kubectl get po -A -o wide | grep [노드 이름] | wc -l
➜ metrics-server 라는 Add-On이 존재하기 때문에 확인 가능
스토리지가 없기 때문에 현재 시점의 리소스 사용량만 확인 가능
$ kubectl get po -A -o wide | grep node1 | wc -l
8
$ kubectl get po -A -o wide | grep node2 | wc -l
10
$ kubectl get po -A -o wide | grep node3 | wc -l
10
🎈 리소스 모니터링
💡 인프라 모니터링 != 애플리케이션 모니터링
리소스 모니터링( == 인프라 모니터링 )
➜ 리소스 사용량만 확인
metrics-server
: 실시간 모니터링Prometheus
: 실시간,이전 cpu/memory/network/disk 모니터링애플리케이션 모니터링
kube-system NS의 metrics server에 의해 리소스 모니터링 가능
$ kubectl get po -n kube-system
...
metrics-server-c57c76cf4-lslwb 1/1 Running 6 (7h32m ago) 2d3h
...
Node1 상세 정보 확인
➜ 현재 사용 가능한 용량, 현재 존재하는 파드 수, CPU Request/Limit, 스토리지 등에 대한 정보 확인 가능
$ kubectl describe node node1
...
Capacity:
cpu: 2
ephemeral-storage: 40593612Ki
hugepages-2Mi: 0
memory: 3927804Ki
pods: 110 # 노드당 파드 최대 갯수 110개
...
# 현재 존재하는 파드 수
Non-terminated Pods: (8 in total)
# 리소스 요청 / 제한 확인 - 모델 유추 가능
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
ingress-nginx ingress-nginx-controller-6pdbx 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d16h
kube-system calico-node-gztj8 150m (8%) 300m (16%) 64M (1%) 500M (14%) 2d16h
kube-system kube-apiserver-node1 250m (13%) 0 (0%) 0 (0%) 0 (0%) 2d16h
kube-system kube-controller-manager-node1 200m (11%) 0 (0%) 0 (0%) 0 (0%) 2d16h
kube-system kube-proxy-48xd9 0 (0%) 0 (0%) 0 (0%) 0 (0%) 37h
kube-system kube-scheduler-node1 100m (5%) 0 (0%) 0 (0%) 0 (0%) 2d16h
kube-system nodelocaldns-zwssl 100m (5%) 0 (0%) 70Mi (2%) 170Mi (5%) 2d16h
metallb-system speaker-nkzm8 0 (0%) 0 (0%) 0 (0%) 0 (0%) 2d16h
...
ephemeral-storage 0 (0%) 0 (0%) # 임시 스토리지 (## 와 동일)
파드별 리소스 사용량 확인
$ kubectl top pods
실행할 수 없는 리소스
➜ 감당 불가능한 리소스
myweb-big.yaml
apiVersion: v1
kind: Pod
metadata:
name: myweb-reqlit
spec:
containers:
- name: myweb-big
image: ghcr.io/c1t1d0s7/go-myweb
resources:
requests:
cpu: 100m
memory: 200M
limits:
cpu: 3000m ✔️
memory: 4000M ✔️
Scale IN/OUT
🎈 AutoScaling
HPA
: Deployment / RS / STS 복제본 개수
(spec.replicas) 조정ClusterAutoScaler
: Worker Node 자동으로 할당
가능 (Add-On)🎈 HPA 리소스 정보
$ kubectl api-resources | grep hpa
horizontalpodautoscalers hpa autoscaling/v1 true HorizontalPodAutoscaler
hpa.spec.maxReplicas
➜ integer
최대 복제 개수
hpa.spec.scaleTargetRef
hpa.spec.targetCPUUtilizationPercentage
➜ integer
cpu가 얼마를 넘어가면 스케일링을 하는가
🎈 레플리카 수 계산
원하는 레플리카 수 = ceil[현재 레플리카 수 * ( 현재 메트릭 값 / 원하는 메트릭 값 )]
ceil
: 올림 함수(천장 함수)
가정 ➜ 현재 cpu : 100% / 원하는 cpu 퍼센트 : 50% / 현재 레플리카 : 3개 / maxReplicas : 10개
➜ 원하는 레플리카 수 : 6개
✔️ 주의! maxReplicas 수 넘지 x
🎈 스케일링의 안정성 (thrashing or flapping)
아무 것도 안하는 상태로 70% 이하
가 되면 최소까지 scale in
함
➜ 안정화 윈도우
사용
이전의 목표 상태를 추론하고 워크로드 수의 원치 않는 변화를 방지 (기본 셋팅 : 300초)
🎈 HPA 실습
myweb-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myweb-deploy
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: myweb
image: ghcr.io/c1t1d0s7/go-myweb
ports:
- containerPort: 8080
resources: ✔️# 이 부분 없었다가 2차 오류 발생 후 수정함
requests:
cpu: 200m
limits:
cpu: 200m
myhpa.yaml
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler ✔️
metadata:
name: myweb-hpa
spec:
minReplicas: 1
maxReplicas: 10 ✔️
targetCPUUtilizationPercentage: 50 ✔️
scaleTargetRef: ✔️
apiVersion: apps/v1 # 추가했어야했음 ✔️
kind: Deployment
name: myweb-deploy
Deployment, HPA 생성
$ kubectl create -f mydeploy.yaml -f myhpa.yaml
생성 확인
$ kubectl get deploy,rs,po,hpa
...
# hpa
NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE
horizontalpodautoscaler.autoscaling/myweb-hpa Deployment/myweb-deploy <unknown>/50% 1 10 0 18s
REPLICAS : 현재 복제본 개수
🎈 오류
1차 오류 : TARGETS : unknown 상태가 바뀌지 않는 오류 발생
➜ hpa 이벤트 로그 확인
원인 : apiVersion: apps/v1 추가했어야했음
$ kubectl describe hpa myweb-hpa
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedGetScale 8s (x10 over 2m23s) horizontal-pod-autoscaler no matches for kind "Deployment" in group ""
2차 오류 : apiVersion 수정하고도 TARGETS : unknown 상태 바뀌지 x
원인 : request 지정 되어있지 않았기 때문(missing request for cpu)
Type Reason Age From Message
---- ------ ---- ---- -------
...
horizontal-pod-autoscaler invalid metrics (1 invalid out of 1),
first error is: failed to get cpu utilization: missing request for cpu ✔️
🌟 limit는 없어도 되지만 request 지정은 필수
배포한 파드 리소스 사용량 확인
$ kubectl top pods
NAME CPU(cores) MEMORY(bytes)
myweb-deploy-6dcf6c95c6-kg8gx 0m 1Mi
myweb-deploy-6dcf6c95c6-vtwvj 0m 1Mi
부하 걸기
$ kubectl exec myweb-deploy-6dcf6c95c6-kg8gx -- sha256sum /dev/zero
cpu 부하 확인
$ kubectl top pods
NAME CPU(cores) MEMORY(bytes)
myweb-deploy-6dcf6c95c6-kg8gx 198m ✔️ 2Mi
myweb-deploy-6dcf6c95c6-vtwvj 0m 1Mi
실시간 파드 스케일링 확인
➜ 다른 터미널에서 진행
$ watch kubectl get po,hpa
💡 Scale IN / Scale OUT
Scale IN
: 300초Scale OUT
: 180초
🎈 v2beta2 버전 AutoScaling
myhpa-v2-beta2.yaml
apiVersion: autoscaling/v2beta2 ✔️
kind: HorizontalPodAutoscaler
metadata:
name: myweb-hpa-beta2
spec:
minReplicas: 1
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
targetCPUUtilizationPercentage: 50
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: myweb-deploy
➕
Pending
: 스케줄링 중 / 이미지 풀링 중 / 볼륨 연결 중