쿠버네티스 개념 간략 정리
쿠버네티스에 대해 간략히 정리해보려고 한다. 상세하게는 말고 기초적인 개념 정도로 정리해보겠다.
쿠버네티스를 언급하기 앞서 도커 컨테이너에 대해서 말을 안할 수가 없다. 컨테이너는 어떤 점이 좋을까?
vm과 비교해서 빠르게 무언가를 띄울 수 있고, 이미지를 사용해서 버전관리도 용이하고 여러 환경에서 동일한 환경을 사용할 수 있다는 장점이 있을 것이다.
컨테이너에 api 서버도 올리고, mysql도 올리고, jenkins도 올리고 사용을 하게 될 것이다.
그렇지만 컨테이너의 수가 많아질수록 하나씩 관리하기가 힘들어질 것이다. api 서버를 여러대 올려놨는데 버전업을 할 때 하나씩 다 해주어야 할 것이고, 어떤 컨테이너가 문제가 생겼는지 체크를 다 해줘야 할 것이다. 컨테이너에 이상이 생겨 다시 생성하는 경우 ip가 변경이 되어서 이로인한 다른 인프라적인 수정이 필요한 경우 또한 맞이할 것이다. 이제 쿠버네티스에 대해 말해보겠다.
쿠버네티스는 컨테이너 오케스트레이션 도구다. 여러 대의 도커 호스트들을 하나의 클러스터로 만들고 이들을 손쉽게 관리하는데 도움을 준다고 받아들이면 되겠다. 위의 문제들을 해결해준다.
쿠버네티스는 대부분의 리소스를 '오브젝트' 라고 불리는 형태로 관리한다.kubectl get api-resources
를 통해 확인할 수 있다.
오브젝트들은 Yaml 파일을 이용해서 생성할 수가 있다.
대표적인 오브젝트에 대해 정리해보겠다.
Pod (포드, 팟)
쿠버네티스에서 사용하는 컨테이너 애플리케이션의 기본단위 이며 가장 작은 배포 단위다.
고유의 클러스터 IP를 가진다.
팟 안에 1개 이상의 컨테이너를 넣어 관리한다. 도커 컨테이너와 유사한데 굳이 왜 사용할까?
팟 내부의 컨테이너들은 네트워와 같은 리눅스 네임스페이스를 공유할 수 있다.
팟은 하나의 완전한 애플리케이션이다. 만약, Nginx 컨테이너가 팟안에 있는 상태라고 가정하자. 이를 위한 부가적인 기능이 필요한 상태인 경우 기능 확장을 위한 추가 컨테이너를 팟에 포함시키고 팟에 정의된 여러 개의 컨테이너를 하나의 완전한 애플리케이션으로서 동작하게 하는 것이다.(팟에 정의된 부가적인 컨테이너를 사이드카 컨테이너라고 부른다)
apiVersion: v1
kind: Pod
metadata:
name: my-nginx-pod
spec:
containers:
- name: my-nginx-container
image: nginx:latest
ports:
- containerPort: 80
protocol: TCP
- apiVersion: 오브젝트의 API 버전 명시한다
- kind: 리소스의 종류를 나타낸다
- metadata: 해당 리소스의 부가 정보인 라벨, 주석, 이름 등과 같은 내용을 나타낸다.
- spec: 리소스를 생성하기 위한 자세한 정보를 나타낸다. 위에서는 내부에 어떤 컨테이너들이 존재하는지 나타낸다.
-
은 list 형식이라고 생각하면 된다.my-nginx-container
라는 이름의nginx:latest
이미지를 가진 컨테이너가 있다는 뜻이고, 포트는 80을 사용한다는 뜻이다.
kubectl apply -f {파일명}
을 통해 생성,kubectl delete -f {파일명}
을 통해 제거한다.
Replica Set (레플리카셋)
레플리카셋은 1개 이상의 팟을 관리하는 오브젝트이다.
동일한 컨테이너를 여러 개 생성하여 운용해야 하는 경우에 일일이 팟을 정의하고 생성하는 것은 비효율적일 것이다. 그리고 각각의 팟이 삭제되거나 장애가 발생하면 다시 생성하지 않는 한 복구가 되지 않을 것이다.
레플리카셋은 이러한 한계점을 해결해준다. 정해진 수의 동일한 팟이 항상 실행되도록 관리해준다.
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: replicaset-nginx
spec:
replicas: 3
selector:
matchLabels:
app: my-nginx-pods-label # 여기까지가 레플리카셋의 정의다.
template: # 여기서부터가 포드의 정의다.
metadata:
name: my-nginx-pod
labels:
app: my-nginx-pods-label
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
- spec.replicas: 동일한 팟을 몇 개 유지시킬 것인지에 대한 정보다.
- spec.selector.matchLabels: 어떠한 라벨들의 팟을 매칭 시킬지에 대한 정보다.
- template: 레플리카셋에서 어떤 팟을 생성할 것인지에 대한 정보다.
spec.selector.matchLabels 에 대해서 이해가 잘 안갈 것이다. 아무런 오브젝트를 만들지 않은 상태에서 위의 Yaml 파일을 실행하면 레플리카셋 1개와 my-nginx-pod
라는 이름의 팟이 3개 생성될 것이다. 그리고 해당 팟의 라벨은 my-nginx-pod
을 가지고 있을 것이다. 그런데 기존에 이미 spec.selector.matchLabels
에 적합한 팟이 존재한다면 레플리카셋은 해당 팟을 관리하고 spec.replicas
의 수를 맞추기 위해 3개가 아닌 2개만 팟을 생성하게 될 것이다. 레플리카셋이 관리할 팟에 대한 매칭 정보라고 생각하면 된다.
팟과 동일한 방법으로 생성 제거를 한다. 레플리카셋을 제거하면 레플리카셋이 관리하고 있는 팟까지도 모두 제거된다.
레플리카셋은 일정 개수의 팟을 유지하는 것을 목적으로 두고 있다.
Deployment (디플로이먼트)
실제 쿠버네티스 환경에서 사용하는 오브젝트다. 디플로이먼트를 생성하면 이에 대응하는 레플리카셋도 함께 생성된다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-nginx-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-nginx
template:
metadata:
name: my-nginx-pod
labels:
app: my-nginx
spec:
containers:
- name: nginx
image: nginx:1.10
ports:
- containerPort: 80
레플리카셋을 정의한 Yaml 파일과 비교해서 kind 밖에 바뀐 것이 없다.
디플로이먼트를 사용하는 핵심적인 이유는 애플리케이션의 업데이트와 배포를 편하게 하기 위해서이다.
위의 파일에서 nginx의 버전업을 하게될 경우 어떤식으로 동작이 진행될까?
새로운 별도의 레플리카셋이 생성되고 변경된 버전의 팟을 생성해 새로운 레플리카셋에 넣고 기존의 레플리카셋에 들어있는 팟의 수를 줄인다. 숫자로 나타내면 다음과 같다.
기존 레플리카셋 팟 숫자 - 새로운 레플리카셋의 팟 숫자. 3 - 0, 3 - 1, 2 - 2, 1 - 3, 0 - 3
kubectl describe deployment {디플로이먼트 네임}
의 명령어를 통해 내부적으로 어떤 이벤트들이 진행되었는지 확인할 수 있다.
그리고 이러한 버전 변경에 대한 정보를 보관하고 있어 쉽게 롤백이 가능하다.kubectl rollout history deployment {디플로이먼트 네임}
버전 정보 조회kubectl rollout undo deployment {디플로이먼트 네임} --to -revision={버전 숫자}
해당 버전으로 변경
일단 기초적인 오브젝트 들에 대해서 파악했다. 추가적인 오브젝트 설명에 앞서 잠시 다른 얘기를 해보겠다.
쿠버네티스는 내부적으로 마스터 노드와 워커 노드로 이루어진다. 각각의 노드는 VM이라고 생각하면 된다. 위에서 언급한 오브젝트들이 정말 멋지게 동작하게끔 도와주는 브레인의 역할이 마스터 노드고, 언급된 오브젝트들이 워커 노드에 속한다.
나는 처음에 minikube을 통해 실습을 했고 여러 vm의 워커 노드들이 있는 환경에서 하지 못했기에 개념이 잘 잡히지 않았다.
지금까지 프로젝트를 진행하면서 각각의 vm에 도커를 띄우고 vm 끼리의 통신을 했기 때문에 어플리케이션은 하나의 vm에 속해야 하고 유일해야 한다고 생각했다. 하지만 쿠버네티스 클러스터링 환경에서는 한 vm, 즉 하나의 노드안에 여러 팟이 존재할 수가 있다.
그냥 vm을 생각하고 이 단어에 빠지지 않는 것이 좋다. 노드에 대해 언급을 한 이유는 이제 설명할 서비스라는 오브젝트 때문이다.
Service (서비스)
디플로이먼트를 통해 생성된 팟의 ip를 통해서 직접 접근할 수도 있지만, 팟이 재생성되거나 그런 경우 ip가 영속적이지 않기 때문에 변경될 수 있다는 점을 유의해야 한다. 여러 개의 디플로이먼트를 하나의 완벽한 애플리케이션으로 연동하려면 팟 IP가 아닌 서로를 발견할 수 있는 방법이 필요하다.
서비스는 여러 개의 팟에 쉽게 접근할 수 있도록 도메인 이름을 부여하고, 로드 밸런서 기능을 수행하고 팟을 외부로 노출시켜준다.
서비스는 Cluster IP, NodePort, LoadBalancer 이렇게 3가지 타입이 있다.
apiVersion: v1
kind: Service
metadata:
name: hostname-svc-clusterip
spec:
ports:
- name: web-port
port: 8080
targetPort: 80
selector:
app: webserver
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hostname-deployment
spec:
replicas: 3
selector:
matchLabels:
app: webserver
template:
metadata:
name: my-webserver
labels:
app: webserver
spec:
containers:
- name: my-webserver
image: bepoz/test:latest
ports:
- containerPort: 80
서비스와 디플로이먼트 파일이다. 디플로이먼트의 파일을 살펴보겠다.
- spec.ports.port: 생성된 서비스는쿠버네티스 내부에서만 사용할 수 있는 고유 IP(Cluster IP)를 할당 받는데, 해당 IP의 어떤 포트를 사용해서 접근 가능한지 기입한다.
- spec.ports.targetPort: 접근할 팟이 사용하고 있는 포트 번호를 나타낸다.
- spec.selector: 이 서비스에서 어떤 라벨을 가지는 팟에 접근할 수 있게 만들 것인지를 나타낸다.
내부적으로 팟의 IP가 변경되어도 서비스를 통해 접근하게 되므로 상관없어질 것이다. Cluster IP는 외부에서 접근할 수가 없다.
apiVersion: v1
kind: Service
metadata:
name: hostname-svc-nodeport
spec:
ports:
- name: web-port
port: 8080
targetPort: 80
selector:
app: webserver
type: NodePort
이번에는 NodePort 타입이다. Cluster IP와는 조금 다르게 이곳에서의 spec.ports.port
는 모든 노드에서 접근할 수 있는 포트를 의미한다. 말로 하기 애매하므로 그림으로 살펴보겠다.
Cluster IP 타입일 때에는 단순히 팟에 연결해주는 것이었다.
NodePort는 외부에서 어떤 노드든 간에 해당 노드의 IP에 접근하고 NodePort의 포트번호로 접근하기만 하면 해당 팟으로 연결을 해준다. 포트는 30000 ~ 32767 중 랜덤으로 지정된다.
로드밸런서 타입은 노드포트에 접근하게끔 로드밸런싱을 해주는 타입인데 클라우드 플랫폼 환경에서만 사용가능하고 잘 쓰이지 않으니 생략하겠다.
Namespace (네임스페이스)
네임스페이스는 쿠버네티스 리소스들을 논리적으로 묶어주는 오브젝트다. 패키지라고 생각하면 된다.
apiVersion: v1
kind: Namespace
metadata:
name: production
위와 같이 파일을 만들거나 kubectl create ns {네임스페이스명}
으로 만들어도 된다.
쿠버네티스 사용 시 자동으로 사용하도록 설정되는 네임스페이스는 default
다. 별도로 옵션을 명시하지 않았다면 해당 네임스페이스를 사용했을 것이다.
kubectl get ns
를 통해 네임스페이스 목록을 조회할 수 있다. 쿠버네티스에 기본적으로 생성되어 있는 여러 네임스페이스들을 맞이할 수 있을 것이다. 예를 들어 kube-system
이라는 네임스페이스에는 쿠버네티스 내부에서 팟, 서비스 등을 이름으로 찾을 수 있도록 하는 DNS 서버의 서비스가 미리 생성되어 있다. 다른 오브젝트들을 조회 시에 특정 네임스페이스에 속한 오브젝트를 조회하고 싶을 때에는 kubectl get po -n {네임스페이스명} (해당 네임스페이스에 속한 pod 조회, 생략 시에는 default 조회)
와 같은 명령어로 조회할 수 있다.
Ingress (인그레스)
인그레스는 외부 요청을 어떻게 처리할 것인지를 정의하는 오브젝트다. 특정 경로로 들어온 요청을 어떤 서비스로 전달할지 정의하는 라우팅 규칙이라든지, 가상 호스트 기반의 요청 처리, SSL/TLS 보안 연결 처리 등을 담당한다.
NodePort와 LoadBalancer를 이용해도 위 기능들을 구현할 수는 있지만 만약 NodePort의 개수가 여러개일 때 각 서비스마다 설정을 해주어야 하는 번거로움이 있을 것이다. 그러나 인그레스를 사용하게 되면 한 곳에만 설정을 하면 되니 편리해진다.
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: ingress-example
spec:
rules:
- host: bepoz.com
http:
paths:
- path: /echo-hostname
backend:
serviceName: hostname-service
servicePort: 80
- host: 해당 도메인 이름으로 접근하는 요청에 대해 처리 규칙을 적용한다는 뜻이다.
- host.paths.path: 해당 경로의 요청을 어떤 서비스로 전달할 것인지를 정의한다.
- backend.serviceName/servicePort: 목적지 서비스의 이름과 포트번호
인그레스는 단지 요청을 처리하는 규칙을 정의하는 선언적인 오브젝트일 뿐, 외부 요청을 받아들일 수 있는 실제 서버가 아니기 때문에 인그레스만 생성한다고 해서 아무 일도 일어나지 않는다. 인그레스는 인그레스 컨트롤러라고 하는 특수한 서버에 적용해야만 그 규칙을 사용할 수 있다. 즉, 실제로 외부 요청을 받아들이는 것은 인그레스 컨트롤러 서버이며, 이 서버가 인그레스 규칙을 로드해 사용한다.
클라우드 서비스를 사용한다면 자사의 로드밸런서 서비스들과 연동해서 인그레스를 사용할 수 있지만, 그렇지 않고 자체적인 클라우드 구축을 하는 경우에는 보통 Nginx 웹 서버 인그레스 컨트롤러를 사용한다. 쿠버네티스에서 공식적으로 개발되고 있기 때문에 설치를 위한 Yaml 파일을 공식 깃허브 저장소에서 직접 내려받을 수 있다.
이렇게 대략적인 오브젝트 설명이 끝났다.
디플로이먼트와 레플리카셋의 동작 기반에는 마스터 노드에서 돌아가고 있는 API Server, etcd, Scheduler, Controller 와 같은 일꾼들이 있기에 가능한 일이다. 이들의 동작과정은 생략하도록 하겠다.
마무리하기 이전에 Service Mesh와 Istio에 대해서 잠시 정리를 하도록 하겠다.
MSA 환경에서 마이크로 서비스 간 서로 통신을 해야한다. 서비스가 많아질수록 서비스 간의 통신은 매우 복잡해질 것이다.
통신 문제와 더불어 단순히 서비스에 필요한 비즈니스 로직 뿐만 아니라 부가적인 설정 정보들이 많이 필요한 상황에서, 마이크로 서비스의 수가 많아질 수록 일일이 설정하기도 힘들고 비효율적이게 될 것이다.
이러한 것을 해결하는 것이 바로 Service Mesh(서비스 메쉬)다.
서비스 메쉬는 애플리케이션의 다양한 부분들이 서로 데이터를 공유하는 방식을 제어하는 방법이다. 마이크로 서비스 간 커뮤니케이션을 매니징하는 것이다.
서비스 메쉬는 위와 같은 형태를 프록시를 이용해 해결한다. 서비스 간의 호출을 프록시 끼리 진행하게 된다.
팟에서도 언급했었지만, 위와 같이 팟에 정의된 부가적인 기능들을 사이드카 라고 부른다.
하지만 위의 형태도 결국에는 서비스의 수가 많아질수록 프록시 또한 많아질 것이고, 관리하기 힘들어질 것이다.
그래서 이 프록시들을 총 관리하는 Control Plane이 생긴 형태를 띈다.
프록시와 컨트롤 플레인은 L7 계층에 속한다. 어쨋든 이것이 바로 서비스 메쉬다.
그렇다면 Istio(이스티오)는 뭘까? 이스티오는 서비스 메쉬를 구현하는 오픈 소스 솔루션이다.
Envoy Proxy라는 것을 이용해서 여러 기능들을 제공해준다. side car injection을 통해 사이드카를 팟 안에 주입해주기도 한다.
Istio의 트래픽 관리 컴포넌트는 크게 Gateway, Virtualservice, DestinationRule 로 나뉘게 된다.
Gateway는 앞에서 보았던 Ingress Controller 역할을 한다고 봐도 될 것 같다. 외부에서 트래픽을 받을 호스트명과 포트, 프로토콜을 설정해준다.
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: ingress-gateway
spec:
selector:
app: ingress-gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
hosts:
- bepoz.example.com
bepoz.example.com으로 443포트를 통해 HTTPS 프로토콜로 받겠다는 뜻이다.
VirtualService는 들어온 트래픽을 서비스로 라우팅하는 기능으로 Ingress의 역할이라고 생각하면 될 것이다.
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: hello-world-api
spec:
hosts:
- bepoz.example.com
gateways:
- ingress-gateway
http:
- match:
- uri:
prefix: /awesome
- route:
- destination:
host: hello-world-api
bepoz.example.com
으로 들어온 요청이고, ingress-gateway
의 게이트웨이로 들어오고 uri의 prefix가 /awesome
인 요청을 hello-world-api
서비스로 보낸다는 뜻이다.
VirtualService에 대한 다른 설정들은 정말 많지만, 모두 언급할 수 없으므로 여기까지만 하겠다.
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: my-destination-rule
spec:
host: my-svc
trafficPolicy:
loadBalancer:
simple: RANDOM
subsets:
- name: v1
labels:
version: v1
- name: v2
labels:
version: v2
trafficPolicy:
loadBalancer:
simple: ROUND_ROBIN
- name: v3
labels:
version: v3
Destination Rule은 어떤 서비스로 보내야 할지 정했던 Virtual Service 와는 달리, Destination Rule은 어떻게 트래픽을 정할 것인지를 정한다. Virtual Service가 특정 서비스로 보내줬다고 해도 해당 서비스 내의 팟을 선택해야 할 것인데, 어느 팟에 트래픽을 보내고 어떻게 보낼지를 정한다고 생각하면 된다.
위 파일은 my-svc
의 서비에 온 트래픽을 처리하는 방법을 나타낸 것이다. default 로드밸런스 트래픽 정책을 RANDOM으로 해두었지만, 팟의 라벨에 따라 v1, v2, v3로 나뉘고 v2인 경우에 라운드 로빈으로 처리하겠다 라고 나타내준 것이다.
전체적인 흐름을 살펴보면 다음과 같다.
(여러 서비스와 여러 팟이 존재하겠지만 그림에서 생략함)
간략하게 정리한다고 한 것이 정리하다보니 길어진 것 같다.
쿠버네티스는 워낙에 방대한 내용이라 이정도의 기본만 잡아두고 필요할 때 찾아가면서 공부하는 것이 바람직해보인다.
끝!
'Infra' 카테고리의 다른 글
[Redis] RedisTemplate, RedisCacheManager 설정에 대해 (2) | 2022.06.15 |
---|---|
[Docker] 계속 잊어버려서 작성하는 도커 사용 간단 정리 (0) | 2022.06.12 |
쿼리 튜닝 미션 적용기 (0) | 2021.10.13 |
[Flyway] 간략히 알아보는 Flyway 적용법 (0) | 2021.10.05 |
[Jenkins] 간략히보는 SonarQube와 Jenkins 연동하기 (0) | 2021.08.06 |