1. 작업 정리 및 이 글을 통해 알아볼 내용
이번 글에서는 쿠버네티스 클러스터에 배포된 파드(pod)가 외부 클라이언트의 요청에 응답할 수 있도록 서비스를 구축하는 방법에 대해 알아보도록 하겠습니다. 먼저 서비스가 어떤 역할을 하는지부터 살펴보겠습니다.
2. 서비스란
쿠버네티스에서 서비스의 역할은 쉽게 말해 로드 밸런서입니다. 서비스는 여러 파드를 담당하는 반장역할을 하며 고정된 IP(Cluster IP) 를 갖습니다. 이 주소로 들어오는 통신을 처리합니다. 내부적으로 여러 파드가 있어도 밖에서는 Cluster IP 만 볼 수 있으며 이 주소로 접근하면 서비스가 통신을 적절히 분배해줍니다. 하지만 Cluster IP 로 서비스가 분배하는 통신은 한 워커 노드 안으로 국한됩니다. 여러 워커 노드 간의 분배는 실제 로드 밸러서(Load Balancer) 또는 인그레스(Ingress)가 담당합니다.
서비스를 이용해 요청을 처리하는 방법은 크게 클러스터 IP, 노드 포트, 인그레스, 로드 밸러서가 있습니다. 이 중 클러스터 IP 서비스는 클러스터 내부에서만 요청을 처리할 수 있으며 노드 포트, 인그레스, 로드 밸런서는 외부의 요청을 처리할 수 있습니다. 이 서비스들에 대해 하나씩 알아보겠습니다.
3. 파드 배포
서비스를 생성하고 동작을 테스트하기 위해서는 우선 파드가 배포되어 있어야 합니다. 이를 위해 UUID 를 응답하는 도커 이미지를 허브에 배포해두었습니다. 여기에서 확인할 수 있으며 해당 도커 이미지를 사용해 워커 노드에 파드를 배포해보겠습니다. 현재 워커노드는 3대 이고, 배포된 파드는 없는 상태입니다.
준비해둔 도커 이미지를 사용하여 3 대의 워커 노드에 파드를 하나 배포해보겠습니다.
kubectl create deployment mc-pods --image=minimalcode/echo-uuid
kubectl scale deployment mc-pods --replicas=3
kubectl get pods -o wide
워커노드에 배포된 파드들 모두 Running 상태로 정상 배포된 것을 확인할 수 있습니다. 서비스 테스트를 하기 위한 파드 준비는 완료되었습니다.
4. 클러스터 IP 서비스 배포
클러스터 IP 서비스를 배포하기 위한 yaml 파일을 작성해보겠습니다. 파일명은 clusterip.yaml 로 내용은 아래와 같습니다.
apiVersion: v1
kind: Service
metadata:
name: cluster-ip-svc
spec:
selector:
app: mc-pods
ports:
- name: http
protocol: TCP
port: 80 # 서비스의 포트
targetPort: 80 # 컨테이너 포트
type: ClusterIP # 클러스터 IP 서비스
작성이 완료되었다면 아래 명령어로 서비스를 생성해보겠습니다.
kubectl create -f ./clusterip.yaml
kubectl get service -o wide
생성된 클러스터 IP 로 curl 테스트를 해본 결과 요청에 대한 부하 분산이 잘 이루어지 것을 확인할 수 있습니다.
이제 다음으로 노드포트 서비스에 대해 알아보기 위해 클러스터 IP 서비스를 삭제하도록 합니다.
kubectl delete service cluster-ip-svc
5. 노드포트 서비스 배포
노드포트 서비스를 배포하기 위한 yaml 파일은 아래와 같습니다. 파일명은 nodeport.yaml 입니다.
apiVersion: v1
kind: Service
metadata:
name: nodeport-svc
spec:
selector:
app: mc-pods
ports:
- name: http
protocol: TCP
port: 80 # 서비스의 포트
targetPort: 80 # 컨테이너 포트
nodePort: 30000 # 워커노드 포트
type: NodePort # 노드포트 타입
노드포트 서비스를 배포하면 yaml 파일에서 지정한 nodePort 의 포트가 모든 워커 노드에서 열립니다. 이 때 설정할 수 있는 nodePort는 30000 ~ 32767 사이의 값입니다. 그리고 워커노드는 자신에게 nodePort로 오는 모든 요청을 노드포트 서비스(nodeport-svc)로 전달합니다. 그러면 노드포트 서비스(nodeport-svc)는 해당 업무를 처리할 수 있는 파드로 요청을 전달합니다. 아래 명령을 실행하여 노드포트 서비스를 생성해보겠습니다.
kubectl create -f ./nodeport.yaml
kubectl get service -o wide
이제 노드포트 서비스 덕분에 워커노드로 30000 포트 요청을 보낼 수 있게 되었습니다. 워커 노드의 IP 를 확인해보겠습니다.
kubectl get nodes -o wide
워커노드 중에서 k8s-worker-1 (10.178.0.41) 로 curl 호출 테스트를 해보겠습니다.
curl 로 호출한 요청에 대해 부하분산이 잘 이루어지는 것을 확인할 수 있습니다. 여기서 한 가지 눈여겨 보아야 할 부분은 바로 네트워크입니다. 현재 10.178.0.0/20 의 네트워크로 curl 호출 테스트를 하는 것을 알 수 있습니다. 이는 쿠버네티스 클러스터를 구축할 때 사용한 10.244.0.0/16 네트워크와는 다른 값입니다. 즉, 클러스터 IP 서비스와 달리 쿠버네티스 클러스터 외부에서 워커노드로 요청을 보낸 것입니다. 이 부분이 노드포트 서비스와 클러스터 IP 서비스의 다른 점입니다. 그래서 워커노드의 30000 포트를 GCP 방화벽에서 오픈하면 외부에서 요청이 가능합니다.
k8s-worker-1 인스턴스의 30000 포트를 오픈하고 외부 IP(34.47.97.67) 로 HTTP 요청했을 때 응답이 잘 오는 것을 확인할 수 있습니다. 그런데 한 가지 이상한 점이 있습니다. k8s-master 서버 쉘에서 curl 로 요청했을 땐 노드포트 서비스에서 부하분산이 되어 응답값이 다르게 왔었는데 포스트맨 또는 브라우저에서 http 요청을 하면 매번 같은 응답이 옵니다. 이 부분과 관련해서 쿠버네티스 인 액션(Kubernetes IN ACTION) 책에서는 이렇게 설명하고 있습니다.
브라우저는 keep-alive 연결을 사용하고 같은 연결로 모든 요청을 보내는 반면, curl은 매번 새로운 연결을 한다. 서비스는 연결 수준에서 동작하므로 서비스에 대한 연결을 처음 열면 임의의 파드가 선택된 다음 해당 연결에 속하는 모든 네트워크 패킷은 모두 같은 파드로 전송된다. 세션 어피니티(session affinity, 세션 고정 기능)가 None으로 설정돼 있어도 사용자는 항상 동일한 파드에 연결된다.(연결이 종료될 때까지)
다음으로 로드밸런서 서비스에 대해 알아보기 위해서 노드포트 서비스를 삭제하도록 합니다.
kubectl delete service nodeport-svc
6. 로드밸런서 서비스 배포
클라우드 플랫폼(AWS, GCP, Azure)에서 제공하는 로드밸런서도 있지만 이 글에서는 온프레미스 로드밸런서 MetalLB 를 설치하여 로드밸런서 서비스를 배포해보도록 하겠습니다.
먼저, metallb 를 설치해줍니다.
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/namespace.yaml
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.11.0/manifests/metallb.yaml
kubectl get pod -n metallb-system -o wide
MetalLB 사용자 설정을 위해 아래와 같이 metallb_config.yaml 파일을 생성하고 배포해줍니다.
apiVersion: v1
kind: ConfigMap
metadata:
namespace: metallb-system
name: config
data:
config: |
address-pools:
- name: nginx-ip-range
protocol: layer2
addresses:
- 10.178.0.11-10.178.0.13 # 사용중인 네트워크 대역에 맞게 수정
kubectl apply -f ./metallb_config.yaml
kubectl get configmap -n metallb-system
이제 로드밸런서 서비스를 생성하여 mc-pods 디플로이먼트를 노출시킵니다.
kubectl expose deployment mc-pods --type=LoadBalancer --name=loadbalancer-svc --port=80
kubectl get services
로드밸런서 서비스가 생성되었습니다. 로드밸런서 서비스의 EXTERNAL-IP 로 curl 요청을 보내서 정상적으로 응답이 오는지 테스트를 해보겠습니다. 아래는 테스트에 사용될 lb_test.sh 쉘 파일입니다.
#!/bin/bash
for var in {1..50}
do
curl 10.178.0.11
done
curl 요청의 응답이 잘 오는 것을 확인할 수 있습니다. 그리고 응답으로 오는 UUID 가 바뀌는 것을 통해 로드밸런서의 부하분산기능이 정상적으로 동작하고 있음을 알 수 있습니다. 로드밸런서 서비스 배포가 완료되었으니 여기에 더해서 오토 스케일링 기능에 대해서도 알아보겠습니다. 오토 스케일링 기능을 사용하려면 먼저 파드의 리소스 사용현황을 확인할 수 있어야 합니다. 아래 명령을 통해 파드의 리소스 사용현황을 확인할 수 있습니다.
kubectl top pods
그런데 막상 명령어를 실행하면 Metrics API 를 사용할 수 없다는 에러 문구가 뜹니다. 이는 metrics-server 가 설치되어 있지 않기 때문입니다. metrics-server 를 설치해보겠습니다.
git clone https://github.com/kubernetes-sigs/metrics-server.git
cd metrics-server/manifests/base
vi deployment.yaml
metrics-server 에 인증 정보 구성이 안되어 있기 때문에 deployment.yaml 파일에서 인증 기능 비활성 옵션을 추가해줍니다.
args:
- --kubelet-insecure-tls
파일 수정이 끝나면 아래 명령어를 실행시켜 metrics-server 를 설치해줍니다.
kubectl apply -k .
아래 명령어를 통해 metrics-server 가 잘 설치되었는지 확인해보겠습니다.
kubectl get deploy metrics-server -n kube-system
kubectl get svc metrics-server -n kube-system
kubectl top nodes
kubectl top nodes --sort-by=memory
metrics-server 가 정상적으로 설치되어 노드의 자원 사용 현황을 확인할 수 있게 되었습니다. 그러면 이제 오토스케일링 기능을 추가해보겠습니다. 현재 쿠버네티스에는 mc-pods 디플로이먼트와 로드밸런서 서비스가 배포되어 있는 상태입니다.
kubectl get deployments
kubectl get services
현재 배포된 mc-pods 디플로이먼트의 spec 을 수정하여 오토 스케일링에 사용될 cpu 기준값을 설정해줍니다.
kubectl edit deployment mc-pods
spec:
containers:
...
resources:
requests: # 단위 m은 milliunits의 약어로 1000m은 1CPU 이다.
cpu: "10m" # CPU 0.01 사용을 기준으로 파드를 증설하도록 설정
limits:
cpu: "50m" # 한쪽파드에 부하가 몰릴 것을 대비해 CPU 사용 제한을 0.05로 설정
...
resources의 수정을 마친후에 vi에서 편집한 것 처럼 :wq 를 누르면 수정한 내용이 반영됩니다. 파드 현황을 보면 스펙이 변경되어 새로운 파드가 생성되는 것을 확인할 수 있습니다.
이제 mc-pods 디플로이먼트에 오토 스케일링을 설정해서 특정 조건을 만족하는 경우에 자동으로 스케일되도록 명령을 실행시켜보겠습니다. 여기서 min은 최소 파드수, max는 최대 파드수, cpu-percent는 CPU 사용량이 50%를 넘게 되면 오토스케일링 하겠다는 의미입니다.
kubectl autoscale deployment mc-pods --min=1 --max=10 --cpu-percent=50
kubectl get hpa
현재는 파드에 부하가 없는 상태라서 CPU가 2m(0.002) 로 나오고 있습니다.
그러면 이제 부하를 발생시켜서 오토 스케일링이 정상적으로 동작하는지 확인해보겠습니다. 좌측 상단에는 watch kubectl top pods 로 파드의 리소스 사용현황을 띄우고, 좌측 하단에는 watch kubectl get pods 로 파드의 현황을 모니터링하겠습니다. 그리고 우측에는 부하를 발생시키는 쉘파일을 실행시켜 curl 요청에 대한 응답을 지켜보도록 하겠습니다. 테스트에 사용될 쉘파일 내용은 아래와 같습니다.
#!/bin/bash
while true :
do
curl 10.178.0.11
done
새로 추가된 파드가 초기에 에러로 응답하는 부분과 관련해서 확인해보던중 파드의 로그를 보니 스프링부트 애플리케이션의 Tomcat 구성단계에서 시간이 오래걸리는 것을 확인하였습니다.
로그를 보면 스프링부트 애플리케이션이 뜨는데 무려 116초나 걸린 것인데 이는 서버 사양이 너무 낮아서 발생한 문제로 보여서 e2-medium(vCPU 2개, 코어 1개, 메모리 4GB) 에서 n1-standard-4(vCPU 4개, 코어 2개, 메모리 15GB) 로 서버 사양을 높인 후 다시 테스트를 진행해보았습니다.
n1-standard-4 로 서버 사양을 업그레이드하고 오토스케일링 테스트 준비를 마쳤습니다. 이번에는 kubectl edit deployment mc-pods 에서 requests.cpu 는 200m(0.2 CPU)로 limits.cpu 를 1000m(1 CPU) 으로 늘렸습니다.
이제 다시 부하테스트를 진행해보겠습니다.
서버 사양 업그레이드를 통해 이전 테스트보다 에러 응답의 빈도가 낮아진 것은 확인할 수 있지만 스프링부트 애플리케이션이 준비되는 동안에는 응답을 하지 못하기 때문에 여전히 에러가 발생하고 있습니다. 이와 관련해서는 파드 투입시 딜레이를 주는 방법 등을 통해서 해결할 수 있는 방법을 추후에 좀 더 살펴보도록 할 예정입니다.
다음으로 인그레스 서비스에 대해 알아보기 위해서 로드밸런서 서비스를 삭제하도록 합니다.
kubectl delete service loadbalancer-svc
kubectl delete hpa mc-pods
7. 인그레스 서비스 배포
마지막으로 알아볼 서비스는 인그레스입니다. 인그레스 서비스를 생성하고 테스트해보기 위해서는 기존 파드에 더하여 새로운 파드가 필요합니다. 요청 URL이 달라짐에 따라 다른 응답을 주는지 확인하기 위해서입니다. 그래서 서버 IP 주소를 응답하는 도커이미지를 여기에 준비해두었습니다. 새로운 파드를 배포해줍니다.
kubectl create deployment mc-ip-pods --image=minimalcode/echo-ip
kubectl scale deployment mc-ip-pods --replicas=3
kubectl get pods
기존 파드에 더하여 새로운 파드가 더해져 총 6개의 파드를 확인할 수 있습니다. 그러면 이제 인그레스 서비스를 설치해보도록 하겠습니다. 인그레스를 사용하려면 인그레스 컨트롤러가 필요한데 이 글에서는 Nginx 인그레스 컨트롤러를 사용하겠습니다. 아래 명령문을 실행하여 인그레스 컨트롤러를 설치해줍니다.
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.6.4/deploy/static/provider/cloud/deploy.yaml
Nginx 인그레스 컨트롤러를 설치하면 기본적으로 ingress-nginx 네임스페이스를 생성하고 사용합니다. 그래서 아래와 같이 명령문을 실행하여 인그레스 컨트롤러가 정상적으로 설치되었는지 확인합니다.
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
그리고 인그레스를 사용자 요구에 맞게 설정하려면 작동방식을 정의해야합니다. ingress-config.yaml 파일을 만들고 인그레스 컨트롤러에 설정을 적용하겠습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: "nginx"
rules:
- http:
paths:
- pathType: Prefix
path: /
backend:
service:
name: mc-svc-default
port:
number: 80
- pathType: Prefix
path: /ip
backend:
service:
name: mc-ip-svc
port:
number: 80
kubectl apply -f ./ingress-config.yaml
Nginx 인그레스 컨트롤러 생성과 인그레스 설정을 완료했습니다. 이제 외부에서 Nginx 인그레스 컨트롤러에 접속할 수 있게 노드포트 서비스로 Nginx 인그레스 컨트롤러를 외부에 노출하겠습니다. 아래의 내용과 같이 ingress.yaml 파일을 만들고 노드포트 서비스를 만들어줍니다.
apiVersion: v1
kind: Service
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
spec:
ports:
- name: http
protocol: TCP
port: 80
targetPort: 80
nodePort: 30100
- name: https
protocol: TCP
port: 443
targetPort: 443
nodePort: 30101
selector:
app.kubernetes.io/name: ingress-nginx
type: NodePort
kubectl apply -f ingress.yaml
kubectl get svc -n ingress-nginx
이제 expose 명령으로 mc-pods 와 mc-ip-pods 디플로이먼트로 서비스로 노출합니다.
kubectl expose deployment mc-pods --name=mc-svc-default --port=80,443
kubectl expose deployment mc-ip-pods --name=mc-ip-svc --port=80,443
kubectl get svc
인그레스 브라우저 테스트를 위해서 인그레스 컨트롤러 노드포트의 사용포트를 방화벽에서 오픈해주도록 하겠습니다. 아래와 같이 30100, 30101 포트를 허용하는 방화벽 규칙을 생성해줍니다.
k8s-worker-1 서버의 수정페이지로 가서 kube-30100, kube-30101 방화벽 태그를 추가하고 저장버튼을 눌러줍니다.
인그레스 테스트를 위해서 브라우저에서 k8s-worker-1의 외부 IP인 34.64.245.194 로 요청을 보냅니다.
인그레스에 설정한 경로에 맞게 정상적으로 파드의 응답이 오는 것을 확인할 수 있습니다. 지금까지 쿠버네티스의 서비스인 클러스터 IP, 노드포트, 로드밸러서, 인그레스에 대해 알아보았습니다. 긴 글 끝까지 읽어주셔서 감사합니다. 👏
다음 글. 쿠버네티스 클러스터 모니터링해보기
'Kubernetes' 카테고리의 다른 글
[Kubernetes] Nginx Ingress Controller 에서 Path 라우팅 적용하기 (0) | 2024.06.22 |
---|---|
[Kubernetes] 쿠버네티스 클러스터 모니터링해보기 [4/4] (0) | 2024.05.25 |
[Kubernetes] 쿠버네티스 클러스터 구축해보기 [2/4] (0) | 2024.04.27 |