컨테이너를 다루는 표준 아키텍처, 쿠버네티스
이전 포스팅에선 쿠버네티스 클러스터 내부에서 파드를 사용했습니다.
그런데 쿠버네티스 클러스터 내부에서만 파드를 이용하려고 쿠버네티스를 배우는 것은 아닙니다.
이번에는 외부 사용자가 파드를 이용하는 방법을 알아보겠습니다.
시작하기에 앞서 혼동되는 용어를 명확하게 짚고 넘어갑시다.
일반적인 서비스 : 웹 서비스나 네트워크 서비스처럼 운영 체제에 속한 서비스 데몬 또는 개발 중인 서비스
쿠버네티스의 서비스 : 외부에서 쿠버네티스 클러스터에 접속하는 방법, 서비스
외부에서 쿠버네티스 클러스터의 내부에 접속하는 가장 쉬운 방법은 노드포트를 서비스를 이용하는 것입니다.
노드포트 서비스를 설정하면 모든 워커 노드의 특정 포트(노드포트)를 열고 여기로 오는 모든 요청을 노트포트 서비스로 전달합니다.
그리고 노드포트 서비스는 해당 업무를 처리할 수 있는 파드로 요청을 전달합니다.
디플로이먼트 파드를 생성합니다.
kubectl create deployment np-pods --image=sysnet4admin/echo-hname
배포된 파드를 확인하고, kubectl create
로 노드포트 서비스를 생성합니다. 여기서는 편의를 위해 이미 정의한 오브젝트 스펙을 이용합니다.
kubectl create -f ~/_Book_k8sInfra/ch3/3.3.1/nodeport.yaml
kind
가 Service
로 바뀌었고, spec에 컨테이너에 대한 정보가 없습니다.type
을 NodePort
로 지정했습니다.apiVersion: v1
kind: Service
metadata:
name: np-svc
spec:
selector:
app: np-pods
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30000
type: NodePort
노드포트 서비스로 생성한 np-svc 서비스를 확인합니다.
kubectl get services
쿠버네티스 클러스터의 워커 노드 IP를 확인합니다.
kubectl get nodes -o wide
호스트 노트북에서 웹 브라우저를 띄우고 확인한 워커 노드의 IP와 30000번(노드포트 포트 번호)으로 접속해 외부에서 접속되는지 확인합니다.
kubectl get pods
배포된 파드에 모든 노드의 노드포트를 통해 외부에서도 접속할 수 있음을 확인했습니다!
조금 더 현실적인 시나리오로 테스트해봅시다.
디플로이먼트로 생성된 파드 1개에 접속하고 있는 중에, 파드가 3개로 증가하면 접속이 어떻게 바뀔까요?
즉, 부하가 분산되는지(로드밸런서 기능) 확인해보겠습니다.
이 명령은 반복적으로 192.168.1.101:30000(w1-k8s의 노드포트)에 접속해 접속한 파드 이름을 화면에 표시(Invoke-RestMethod)합니다.
이렇게 하면 파드가 1개에서 3개로 늘어나는 시점을 관찰할 수 있습니다.
먼저 파워셸(powershell)을 설치합니다.
명렁어를 입력합니다.
$i=0; while($true)
{
% { $i++; write-host -NoNewline "$i $_" }
(Invoke-RestMethod "http://192.168.1.101:30000")-replace '\n', " "
}
명령을 실행하면 위와 같이 현재 접속한 호스트 이름을 순서대로 출력합니다.
kubectl scale deployment np-pods --replicas=3
kubectl get pods
신기합니다. 어떻게 추가된 파드를 외부에서 추적해 접속하는 것일까요?
이는 오드포트 오브젝트 스펙에 적힌 np-pods와 디플로이먼트의 이름을 확인해 동일하면 같은 파드라고 간주하기 때문입니다.
추적 방법은 많지만, 여기서는 가장 간단하게 이름으로 진행했습니다.
spec:
selector:
app: np-pods
노드포트 서비스는 오브젝트 스펙 파일로만 생성하는 걸까요?
아닙니다, 노드포트 서비스는 expose
명령어로도 생성할 수 있습니다.
np-svc-v2
로, 타입은 NodePort
로 지정합니다.(서비스 타입은 반드시 대소문자를 구분해야 합니다!, 구분하지 않으면 invaild 오류가 발생합니다.)kubectl expose deployment np-pods --type=NodePort --name=np-svc-v2 --port=80
kubectl get services
kubectl delete deployment np-pods
kubectl delete services np-svc
kubectl delete services np-svc-v2
노드포트 서비스는 포트를 중복 사용할 수 없어서 1개의 노드포트에 1개의 디플로이먼트만 적용됩니다.
그렇다면 여러 개의 디플로이먼트가 있을 때 그 수만큼 노드포트 서비스를 구동해야 할까요??
쿠버네티스는 이런 경우에 인그레스를 사용합니다.
인그레스 컨트롤러 서비스가 파드와 직접 통신하지 못하고 노드포트 서비스와 연동하여 실행되는 개념이 어려운데요, 실습을 진행하며 이해해보도록 합시다!
kubectl create deployment in-hname-pod --image=sysnet4admin/echo-hname
kubectl create deployment in-ip-pod --image=sysnet4admin/echo-ip
kubectl get pods
kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-nginx.yaml
-n ingress-nginx
옵션을 추가해야 합니다.-n
은 namespace의 약어로, default 외의 네임스페이스를 확인할 떄 사용하는 옵션입니다. kubectl get pods -n ingress-nginx
kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-config.yaml
인그레스를 위한 설정 파일은 다음과 같습니다. 이 파일은 들어오는 주소 값과 포트에 따라 노출된 서비스를 연결하는 역할을 설정합니다.
외부에서 주소 값과 노드포트를 가지고 들어오는 것은 hname-svc-default
서비스와 연결된 파드로 넘기고, 외부에서 들어오는 주소 값, 노드포트와 함께 뒤에 /ip
를 추가한 주소 값은 ip-svc
서비스와 연결된 파드로 접속하게 설정했습니다.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
# Ingress의 이름
# 이름을 통해서 통신할 ingress 컨트롤러를 확인
name: ingress-nginx
# 메타테이터의 기록 및 변경
# 여기선 rewrite-target을 /(기본주소)로 지정함
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
# 규칙을 지정
rules:
- http:
paths:
# 기본 경로 규칙
- path:
# 연결되는 서비스와 포트
backend:
serviceName: hname-svc-default
servicePort: 80
# 기본 경로에 ip라는 이름의 경로 추가
- path: /ip
# 연결되는 서비스와 포트
backend:
serviceName: ip-svc
servicePort: 80
# 기본 경로에 your-directory 경로 추가
- path: /your-directory
# 연결되는 서비스와 포트
backend:
serviceName: your-svc
servicePort: 80
kubectl get ingress
kubectl get ingress -o yaml
kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.2/ingress.yaml
ingress-nginx
로 지정하고 NGINX 인그레스 컨트롤러의 요구 사항에 따라 셀릭터를 ingress-nginx
로 지정했습니다.apiVersion: v1
kind: Service
metadata:
# 서비스 이름
name: nginx-ingress-controller
# 네임스페이스 이름
namespace: ingress-nginx
spec:
# 사용할 프로토콜과 포트들을 지정
ports:
# http에 대한 프로토콜 및 포트 지정
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30100
# https에 대한 프로토콜 및 포트 지정
- name: https
protocol: TCP
port: 443
targetPort: 443
nodePort: 30101
# 셀렉터의 레이블 지정
selector:
app.kubernetes.io/name: ingress-nginx
# 서비스 타입을 설정
type: NodePort
-n ingress-nginx
로 네임스페이스를 지정해야만 내용을 확인할 수 있습니다.kubectl get services -n ingress-nginx
kubectl expose deployment in-hname-pod --name=hname-svc-default --port=80,443
kubectl expose deployment in-ip-pod --name=ip-svc --port=80,443
-n
옵션으로 네임스페이스를 지정하지 않아도 됩니다.kubectl get services
192.168.1.101:30100
에 접속해 외부에서 접속되는 경로에 따라 다르게 작동하는지 확인합니다.192.168.1.101:30100/ip
로 접속해봅니다. https://192.168.1.101:30101
로 접속해 https 연결도 정상적으로 작동하는지 확인합니다.https://192.168.1.101:30101/ip
를 입력해 마찬가지로 요청 방법과 파드의 IP 주소가 표시되는지 확인합니다.kubectl delete deployment in-hname-pod
kubectl delete deployment in-ip-pod
kubectl delete services hname-svc-default
kubectl delete services ip-svc
kubectl delete -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-nginx.yaml
kubectl delete -f ~/_Book_k8sInfra/ch3/3.3.2/ingress-config.yaml
앞에서 배운 연결 방식은 들어오는 요청을 모두 워커 노드의 노드포트를 통해 노드포트 서비스로 이동하고, 이를 다시 쿠버네티스의 파드로 보내는 구조였습니다.
이 방식은 매우 비효율적입니다.
그래서 쿠버네티스에는 로드밸런서(LoadBalancer) 라는 서비스 타입을 제공해 간단한 구조로 파드를 외부에 노출하고 부하를 분산합니다.
그런데 왜 먼저 로드밸런서를 사용하지 않았을까요?
로드밸런서를 사용하려면 로드밸런서를 이미 구현해 둔 서비스업체의 도움을 받아 쿠버네티스 클러스터 외부에 구현해야 하기 떄문입니다.
클라우드에서 제공하는 쿠버네티스를 사용하고 있다면 다음과 같이 선언만 하면 됩니다.(그래서 이 실습은 EKS, GKE, AKS에서만 가능합니다. 추후에 AKS를 구축해보게 된다면 실습해보도록 하죠!)
그러면 쿠버네티스 클러스터에 로드밸런서 서비스가 생성돼 외부와 통신할 수 있는 IP(EXTERNAL-IP)가 부여되고, 외부와 통신할 수 있으며 부하도 분산됩니다.
kubectl expose deployment ex-lb --type=LoadBalancer --name=ex-svc
kubectl get services ex-svc
그렇다면 우리가 만든 테스트 가상 환경(온프레미스)에서는 로드밸런서를 사용하는 것은 불가능할까요?
대안을 알아봅시다!
온프레미스에서 로드밸런서를 사용하려면 내부에 로드밸런서 서비스를 받아주는 구성이 필요한데, 이를 지원하는 것이 MetalLB입니다.
MetalLB
MetalLB controller
MetalLB speaker
구성을 확인했으니 MetalLB로 온프레미스 쿠버네티스 환경에서 로드밸런서 서비스를 사용하도록 구성해보겠습니다!
디플로이먼트를 이용해 2종료(lb-hname-pods, lb-ip-pods)의 파드를 생성합니다. 그리고 scale 명령으로 파드를 3개로 늘려 노드당 1개씩 파드가 배포되게 합니다.
kubectl create deployment lb-hname-pods --image=sysnet4admin/echo-hname
kubectl scale deployment lb-hname-pods --replicas=3
kubectl create deployment lb-ip-pods --image=sysnet4admin/echo-ip
kubectl scale deployment lb-ip-pods --replicas=3
2종류의 파드가 3개씩 총 6개가 배포됐는지 확인합니다.
kubectl get pods
인그레스와 마찬가지로 사전에 정의된 오브젝트 스펙으로 MetalLB를 구성합니다. 이렇게 하면 MetalLB에 필요한 요소가 모두 설치되고 독립적인 네임스페이스(metallb-system)도 함께 만들어집니다.
kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.4/metallb.yaml
배포된 MetalLB의 파드가 5개(controller 1개, speaker 4개)인지 확인하고, IP와 상태도 확인합니다.
kubectl get pods -n metallb-system -o wide
인그레스와 마찬가지로 MetalLB도 설정을 적용해야 하는데, 다음 방법으로 적용합니다. 이때 오브젝트는 ConfigMap을 사용합니다.
kubectl apply -f ~/_Book_k8sInfra/ch3/3.3.4/metallb-l2config.yaml
apiVersion: v1
kind: ConfigMap
metadata:
# 네임스페이스 이름
namespace: metallb-system
# 컨피그맵 이름
name: config
data:
# 설정내용
config: |
# metallb의 세부 설정
address-pools:
- name: nginx-ip-range
# metallb에서 제공하는 로드밸런서의 동작 방식
protocol: layer2
# metallb에서 제공하는 로드밸런스의 Ext 주소
addresses:
- 192.168.1.11-192.168.1.13
ConfigMap이 생성됐는지 확인합니다.
kubectl get configmap -n metallb-system
-o yaml
옵션을 주소 다시 실행해 MetalLB의 설정이 올바르게 적용됐는지 확인합니다.
kubectl get configmap -n metallb-system -o yaml
모든 설정이 완료됐으니 이제 각 디플로이먼트(lb-hname-pods, lb-ip-pods)를 로드밸런서 서비스로 노출합니다.
kubectl expose deployment lb-hname-pods --type=LoadBalancer --name=lb-hname-svc --port=80
kubectl expose deployment lb-ip-pods --type=LoadBalancer --name=lb-ip-svc --port=80
생성된 로드밸런서 서비스별로 CLUSTER-IP와 EXTERNAL-IP가 잘 적용됐는지 확인합니다. 특히 EXTERNAL-IP에 ConfigMap을 통해 부여한 IP를 확인합니다!
kubectl get services
EXTERNAL-IP가 잘 작동하는지 확인해 봅시다. 브라우저에서 192.168.1.11에 접속합니다. 배포된 파드 중 하나의 이름이 브라우저에 표시되는지 확인하세요!
이번엔 192.168.1.12로 접속해 파드의 요청 방법과 IP가 표시되는지 확인합니다.
파워셸 명령 창을 띄우고 셸 스크립트를 실행합니다. 로드밸런서 기능이 정상적으로 작동하면 192.168.1.11에서 반복적으로 결괏값을 가져옵니다.
$i=0; while($true)
{
% { $i++; write-host -NoNewline "$i $_" }
(Invoke-RestMethod "http://192.168.1.11")-replace '\n', " "
}
scale 명령으로 파드를 6개로 늘립니다.
kubectl scale deployment lb-hname-pods --replicas=6
늘어난 파드 6개에도 EXTERNAL-IP를 통해 접근되는지 확인합니다.
kubectl get pods
온프레미스에서도 로드밸런서를 사용할 수 있게 하는 MetalLB를 구성해봤습니다. 다음 실습을 진행하기 전에 배포한 디플로이먼트와 서비스는 삭제합니다. 단, MetalLB 설정은 계속 사용하므로 삭제하지 않겠습니다.
kubectl delete deployment lb-hname-pods
kubectl delete deployment lb-ip-pods
kubectl delete service lb-hname-svc
kubectl delete service lb-ip-svc
지금까지는 사용자 1명이 파드에 접근하는 방법을 알아봤습니다.
그런데 사용자가 갑자기 늘어난다면 어떻게 될까요?
파드가 더 이상 감당할 수 없어서 서비스 불가(여기서 서비스는 쿠버네티스의 서비스가 아닙니다!)라는 결과를 초래할 수도 있습니다.
쿠버네티스는 이런 경우를 대비해 부하량에 따른 디플로이먼트의 파드 수를 유동적으로 관리하는 기능을 제공합니다.
이를 HPA(Horizontal Pod Autoscaler) 라고 합니다. HPA를 어떻게 설정하고 사용하는지 알아봅시다!
디플로이먼트 1개를 hpa-hname-pods
라는 이름으로 생성합니다.
kubectl create deployment hpa-hname-pods --image=sysnet4admin/echo-hname
앞에서 MetalLB를 구성했으므로 바로 expose를 실행해 hpa-hname-pods
를 로드밸런서 서비스로 바로 설정할 수 있습니다.
kubectl expose deployment hpa-hname-pods --type=LoadBalancer --name=hpa-hname-svc --port=80
설정된 로드밸런서 서비스와 부여된 IP를 확인합니다.
kubectl get services
HPA가 작동하려면 파드의 자원이 어느 정도 사용되는지 파악해야 합니다. 부하를 확인하는 명령은 리눅스의 top(table of processes)과 비슷한 kubectl top pods
입니다.
kubectl top pods
서비스에서와 마찬가지로 메트릭 서버도 오브젝트 스펙 파일로 설치할 수 있습니다.
git clone
이후에 디렉터리에 있는 파일들을 다시 실행하야 하는 번거로움이 있습니다.kubectl create -f ~/_Book_k8sInfra/ch3/3.3.5/metrics-server.yaml
containers:
- name: metrics-server
image: k8s.gcr.io/metrics-server-amd64:v0.3.6
args:
# Manually Add for lab env(Sysnet4admin/k8s)
# skip tls internal usage purpose
# TLS(Transport Layer Security)를 무시하게 합니다.
- --kubelet-insecure-tls
# kubelet could use internalIP communication
# kubelet이 내부 주소를 우선 사용하게 합니다.
- --kubelet-preferred-address-types=InternalIP
- --cert-dir=/tmp
- --secure-port=4443
kubectl top pods
error: Metrics not available for pod default/hpa-hname-pods-~~, age:~~
메트릭 서버를 설정하고 나면 kubectl top pods
명령의 결과를 제대로 확인할 수 있습니다.
edit
명령으로 직접 수정합니다.edit
명령을 실행해 배포된 디플로이먼트 내용을 확인합니다.
resources: {}
부분에서 {}을 생략하고 그 아래에 다음과 같이 requests, limits
항목과 그 값을 추가합니다. 이때 추가한 값은 파드마다 주어진 부하량을 결정하는 기준이 됩니다.kubectl edit deployment hpa-hname-pods
일정 시간이 지난 후 kubectl top pods
를 실행하면 스펙이 변경돼 새로운 파드가 생성된 것을 확인할 수 있습니다.
hpa-hname-pods에 autoscale
을 설정해서 특정 조건이 만족되는 경우 자동으로 scale
명령이 수행되도록 하겠습니다.
min
은 최소 파드의 수, max
는 최대 파드의 수입니다. cpu-percent
는 CPU 사용량이 50%를 넘기면 autoscale하겠다는 의미입니다.kubectl autoscale deployment hpa-hname-pods --min=1 --max=30 --cpu-percent=50
* HPA를 통해 늘어나는 파드 수 계산 방법
HPA는 다음과 같은 바업ㅂ으로 파드의 증가 또는 감소를 조절합니다.
디플로이먼트 스펙에서 resources의 CPU를 10으로 설정하고, autoscale에서 cpu-percent를 50%로 했다고 가정하겠습니다.
- `kubectl top pods`로 확인된 파드의 메트릭이 다음과 같다고 해봅시다.
`CPU(cores) : 29m, MEMORY(bytes) : 1Mi`
- 파드는 29m이라는 부하를 받고 있습니다. 1개의 파드가 처리할 수 있는 부하는 10m이고,
CPU 부하량이 50%가 넘으면 추가 파드를 생성해야 하므로 부하가 5m이 넘으면 파드를 증설하게 돼 있습니다.
따라서 29m/5를 하고 올림하면 6이라는 숫자가 나옵니다. 결국 증가하는 파드의 수를 6입니다.
이때 부하 총량을 가지고 HPA가 작동하기 때문에 일부 파드는 5m을 넘을 수도 있습니다.
- 예를 들어 1개의 파드에서만 다음과 같이 부하(27m)가 발생한다면 해당 부하는 분산되지 않습니다.
따라서 부하 부산을 위해서는 쿠버네티스 서비스를 통해 파드 그룹인 디플로이먼트에 도달해야 합니다.
- `kubectl get hpa`를 실행하면 HPA의 현재 상태를 요약해서 보여줍니다.
테스트를 위해 마스터 노드 창과 파워셸 창을 띄웁니다.
watch kubectl top pods
, watch kubectl get pods
) 파워셸은 HPA를 테스트합니다.HPA를 테스트하기 위해 반복문을 실행합니다. 부하를 주는 명령은 로드밸런서를 테스트 했던 코드와 동일합니다. 마스터 노드에서는 부하량을 감지하는지 확인합니다.
마스터 노드 : watch kubectl top pods
파워셸 :
$i=0; while($true)
{
% { $i++; write-host -NoNewline "$i $_" }
(Invoke-RestMethod "http://192.168.1.11")-replace '\n', " "
}
부하량이 늘어남에 따라 파드가 새로 생성되는지 확인합니다.
watch kubectl get pods
실행부하 분산으로 생성된 파드의 부하량이 증가하는지 확인합니다.
watch kubectl top pods
실행더 이상 파드가 새로 생성되지 않는 안정적인 상태가 되는 것을 확인하고(저는 파드가 21개가 되니 안정적인 상태가 되었습니다. 부하량은 대체로 5~6m입니다.) 부하를 생성하는 파워셸 창을 종료합니다.
일정 시간이 지난 후 더 이상 부하가 없으면 autoscale
의 최소 조건인 파드 1개의 상태로 돌아가기 위해 파드가 종료되는 것을 확인합니다. 이번에는 시간이 좀 더 걸립니다.
watch kubectl get pods
로 보면 Terminating 상태로 변하고 사라지는 것을 볼 수 있고, watch kubectl top pods
를 보면 각 파드의 CPU 부하량이 바뀌고 삭제되는 것을 확인 할 수 있습니다. 사용하지 않는 파드는 모두 종료되고 최솟값인 1개만 남습니다.
부하 테스트가 끝났습니다. 파드 부하량에 따라 HPA가 자동으로 파드 수를 조절하는 것을 확인했습니다. HPA를 잘 활용하면 자원의 사용을 극대화하면서 서비스 가동률을 높일 수 있습니다. 앞에서와 마찬가지로 생성한 디플로이먼트, 서비스, 메트릭 서버를 삭제합니다. MetalLB는 계속 사용하므로 삭제하지 않습니다.
kubectl delete deployment hpa-hname-pods
kubectl delete hpa hpa-hname-pods
kubectl delete service hpa-hname-svc
kubectl delete -f ~/_Book_k8sInfra/ch3/3.3.5/metrics-server.yaml
쿠버네티스에서 파드를 생성한 후에 실제로 쿠버네티스 외부의 사용자들이 쿠버네티스 내부에 있는 파드에 접속할 수 있도록 경로를 만들어주는 여러 가지 종류의 서비스들을 살펴봤습니다.
다음 포스팅에서는 마지막으로 디플로이먼트 외의 다른 오브젝트를 사용해 보겠습니다!
본 게시물은 "컨테이너 인프라 환경 구축을 위한 쿠버네티스/도커 - 조훈,심근우,문성주 지음(2021)" 기반으로 작성되었습니다.