클라이언트와 서버 관계에서, 상태를 보존하는 형태의 서비스이다. 말 그대로 상태를 저장하는 형태의 애플리케이션으로 대표적으로 Mongodb, Redis, Mysql 과 같은 Database가 있다.반대 의미로 상태를 보존하지 않는 서비스는 Stateless 라고 하는데, 대표적으로 Stateless 서비스는 웹서버 종류 Apache, Nginx 등이 있다.
예를들어 Mysql에 Write와 Read 구성된 클러스터가 있다고 생각을 해보자. 또한 Stateless 앱은 Volume을 만들 때 하나의 Volume을 보면 되지만 Stateful 앱은 Volume 또한 각자 다르게 만들어진다. 그래야 Master가 다운 됬을 때 임시로 Slave에 연결을 시켜줄 수 있기 때문이다. 또한 네트워크 부분에서도 차이가 있다. Stateless는 kube-proxy에서 트래픽을 분산시켜주지만 Stateful은 사용 목적에 따라 트래픽이 들어 온다. 가령 Read 인 Database에 연결을 하려면 Read Pod로 Write인 Database에 연결을 하려면 Write Pod로 들어온다.
만약에 Replicaset으로 Pod를 만들면 랜덤한 이름이 부여 되어서 동시에 Pod가 뜨게 되지만 Statefulset을 통해 Pod를 만들면 인덱스 번호로 0,1,2 이런식으로 생기고 Pod가 순차적으로 생성이 된다.
또한 삭제도 마찬가지 인데 ReplicaSet은 한번에 Pod를 전부 삭제하고 StatefulSet은 인덱스가 높은 Pod 부터 하나씩 삭제한다.
ReplicaSet은 보통 PVC를 직접 생성하고 Pod를 연결을 하는데, StatefulSet은 volumeClaimTemplate을 통해서 PVC를 동적으로 생성해준다. 그리고 Replicaset을 3으로 변경하면 ReplicaSet은 똑같은 PVC와 연결이 되지만, StatefulSet은 새로운 PVC 3개를 생성한다.
StatefulSet은 Headless Service와 연결을 한다는 점이다. 왜냐하면 Pod에 도메인이 생기기 때문에 쿠버네티스 클러스터 internal server 안에서 특정 Pod가 Stateful 한 Pod에 연결 할 수 있다.
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: stateful-pvc
spec:
replicas: 3
selector:
matchLabels:
type: db
serviceName: "stateful-headless"
template:
metadata:
labels:
type: db
spec:
containers:
- name: container
image: kubetm/app
volumeMounts:
- name: volume
mountPath: /applog
terminationGracePeriodSeconds: 10
volumeClaimTemplates:
- metadata:
name: volume
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1G
storageClassName: "fast"
이런식으로 RelicaSet을 생성하면 Stateful 한 Pod가 3개가 생성이 될테고 Pod를 삭제 하게 되면 10초 단위로 삭제되고 Pod와 연결되는 PVC 는 3개가 생성된다. (물론 PV 도 3개 생성됨)
여기서 Headless Service를 만들어서 Pod에게 도메인 주소를 줄 수 있다.
apiVersion: v1
kind: Service
metadata:
name: stateful-headless
spec:
selector:
type: db
ports:
- port: 80
targetPort: 8080
clusterIP: None
spec.selector를 StatefulSet의 matchLabel과 맞춰주면 Headless Service와 연결이 된다.
만약에 e-commerce 서비스를 MSA 구조로 개발을 했다고 가정을 해보자. Shoping Service, CX Service, Order Service 가 나눠져 있는 상태에서 Shopping Service가 죽어도 CX Service, Order Service는 정상적으로 작동 할 것 이다. 이게 가능 하려면 각각의 Service에 Ingress를 통해 연결을해서 도메인을 다르게 해서 각각 다른 Service에 접근 할 수 있다.
Ingress 는 path 에 따라 (ex. /shop => shopping service, /order => order service, /customer => CX service) 원하는 Pod로 트래픽을 흘려 보낼 수 있다. 하지만 Ingress 만 있다고 해서 트래픽을 흘려 보내주진 않는데 Ingress Controller 라는 플러그인을 설치 해야 한다. Nginx Ingress Controller를 설치 했다고 가정을 해보면 nginx라는 namespace가 생성되고 Deployment 가 생성이 되어 실제 Ingress 구현체인 Nginx Pod가 생성이 된다. 그 다음 해당 Pod가 Ingress Rule이 있는지 확인하고 만약에 있다면 경로 마다 Service를 연결 시켜준다. 여기서 Nginx Ingress의 구현체가 Pod 이기 때문에 Service를 붙혀서 (Nodeport 나 LoadBalancer) 사용해야 한다.
또한 Ingress를 통해서 https 도 설정을 할 수 있는데, Nginx Ingress Controller의 Pod를 443 포트를 사용하고 실제 Ingress 를 host 와 serviceName을 통해 호스트와 서비스를 연결하고 tls 라는 옵션이 있는데 여기에 Secret 오브젝트와 연결을 시켜준다.
일단 Ingress Controller를 설치한다. (공식문서)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.0.0/deploy/static/provider/baremetal/deploy.yaml
이렇게 생성하면 Ingress Controller의 Pod와 Service를 생성해준다.
그 다음 Shopping, Custmer, Order Pod와 Service를 만들어 보겠다.
apiVersion: v1
kind: Pod
metadata:
name: pod-shopping
labels:
category: shopping
spec:
containers:
- name: container
image: kubetm/shopping
---
apiVersion: v1
kind: Service
metadata:
name: svc-shopping
spec:
selector:
category: shopping
ports:
- port: 8080
apiVersion: v1
kind: Pod
metadata:
name: pod-customer
labels:
category: customer
spec:
containers:
- name: container
image: kubetm/customer
---
apiVersion: v1
kind: Service
metadata:
name: svc-customer
spec:
selector:
category: customer
ports:
- port: 8080
apiVersion: v1
kind: Pod
metadata:
name: pod-order
labels:
category: order
spec:
containers:
- name: container
image: kubetm/order
---
apiVersion: v1
kind: Service
metadata:
name: svc-order
spec:
selector:
category: order
ports:
- port: 8080
이렇게 Pod를 3개 만들고 Igress 를 만들면 각각의 서비스에 연결이 된다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: service-loadbalancing
spec:
ingressClassName: nginx
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: svc-shopping
port:
number: 8080
- path: /customer
pathType: Prefix
backend:
service:
name: svc-customer
port:
number: 8080
- path: /order
pathType: Prefix
backend:
service:
name: svc-order
port:
number: 8080
쿠버네티스의 Autoscaler 의 종류는 3개가 있다.
쿠버네티스 클러스터에는 기본적으로 Master Node와 Worker Node가 있는데, Master Node에 Control Plane Component라고 해서 쿠버네티스 클러스터의 주요기능들이 Pod형태로 띄워져서 돌아간다.
HPA와 관련있는 Component를 설명해보면 일단 Master Node에선 다음과 같다.
Worker Node Component는 다음과 같다.
이런 구조의 쿠버네티스 클러스터에서 사용자가 ReplicaSet을 만든다고 가정해보면 Controller Manager에서 ReplicaSet을 담당하는 쓰레드가 하나의 Pod를 만들어달라고 kube-apiserver에 요청을 보낸다음, kube-apiserver는 kubelet에 요청을 한다. 그럼 kubelet은 docker를 만들어 달라고 Controller Runtime에게 요청을 하면 Controller Runtime은 Node 위에 실제 Container를 만들게 된다.
여기서 HPA는 어떻게 동작을 하냐면, Resource Estimator가 Docker의 Memory와 CPU의 정보를 체크하는데 이 정보를 kubelet을 통해 가져갈 수 있도록 해놓았다. 그리고 AddOn으로 metrics-server를 설치 해야하는데, metrics-server는 각각 Node들의 Memory와 CPU 데이터를 가져온다. 그 다음 meterics-server는 kube-apiserver에 이 데이터를 등록을 해놓는다. 그럼 Controller Manager에서 돌아가는 HPA는 kube-apiserver를 통해서 10~15초 마다 상태를 체크할수 있게되고 Pod의 리소스를 확인하고 scale-out scale-in 을 할수 있다. 추가로 Prometheus 를 설치하면 Ingress로 들어오는 리소스 정보를 알 수 있어서 이 정보를 기반으로 HPA를 셋팅 해놓을 수 있다.
Deployment를 replicas를 2로 만들고 limits cpu를 500m requests cpu를 200m 으로 만들었다고 가정을 해보자. 또한 이를 바라보고 있는 HPA 또한 만들텐데 target으론 Deployment를 지정할 것이고, maxReplicas와 minReplicas를 지정 해야한다. 또한 metrics의 type을 지정헤야 하는데 어떤 정보를 기반으로 scale-out 할것인지 결정하는 부분이다. Resource로 지정해 놓는다고하면 cpu와 memory 값을 셋팅 해놔야 하고, Request 단위로 Scale Out을 지정할 수도 있는데 이는 Object로 지정을하면 된다. (Prometheus가 있는 경우에만) 마지막으로 어떤 조건으로 scale-out 해야할지를 지정해야 하는데 Resource의 기준으로 보면 가장 보편적으로 사용되는게 Utilization 인데 averageUtilization을 50으로 두면 현재 사용 자원이 평균 50%를 넘으면 replicas를 증가시킨다. 또한 Scale In도 평균보다 자원을 많이 쓰고 있지 않으면 이 값을 통해서 scale in을 해준다. Uilization 말고 Averagevalue라는 것도 있는데 이 타입은 평균수치를 넣지 않고 정말 평균값을 넣어주면 된다 (ex 100m)
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml
이것을 통해 metric-server를 클러스터 안에 설치할 수 있다.
먼저 deploymet와 service를 생성해보자.
apiVersion: apps/v1
kind: Deployment
metadata:
name: stateless-cpu
spec:
selector:
matchLabels:
resource: cpu
replicas: 2
template:
metadata:
labels:
resource: cpu
spec:
containers:
- name: container
image: kubetm/app:v1
resources:
requests:
cpu: 10m
limits:
cpu: 20m
---
apiVersion: v1
kind: Service
metadata:
name: stateless-svc
spec:
selector:
resource: cpu
ports:
- port: 8080
targetPort: 8080
nodePort: 30001
type: NodePort
그 다음 Utilization을 통해 Autoscaling 하는 HPA 를 만들어 보겠다.
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-resource-cpu
spec:
maxReplicas: 10
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: stateless-cpu
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50
scaleTargetRef를 통해 Deploymet를 지정해주고 실제로 부하를 주면 잘 scale out이 되고 부하를 줄이면 scale in이 되는것을 확인할 수 있다.
만약에 Object를 통해 Request Per Second로 Auto Scale 하고 싶으면 HPA를 다음과 같이 작성 하면된다.
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: hpa-object
spec:
maxReplicas: 10
minReplicas: 2
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: stateless-app1
metrics:
- type: Object
object:
metric:
name: requests-per-second
target:
type: Value
value: 10
describedObject:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
name: ingress-hpa