컨테이너
하나의 호스트에서 컨테이너 이미지를 각각 격리된 별도의 프로세스를 실행시켜 주는 경량 가상화 기술, 운영체제에서 사용되는 경량 가상화 기술이다.
컨테이너 이미지
- 애플리케이션과 애플리케이션이 실행되는데 필요한 런타임 환경, 라이브러리, OS 환경 등 모든 의존성이 포함되어 있다. 그렇기 때문에 노트북, 서버, 클라우드 등 어디서든 컨테이너 이미지만 있다면 OS 환경이 달라도 개발했던 환경과 동일한 환경으로 실행될 수 있다.
컨테이너 이미지가 컨테이너로 실행되면, 격리된 프로세스로 실행되기 때문에 호스트 운영체제에는 접근할 수 없고, 마치 별도의 운영체제가 설치된 서버인 것처럼 사용할 수 있게 해 준다.
Q. 격리된 프로세스로 실행된다.
A. 단순히 실행에 필요한 파일 시스템의 격리뿐만 아니라, 해당 컨테이너가 사용할 수 있는 네트워크나 스토리지 등과 같은 장치와 CPU, 메모리 등의 성능 사용량에 대한 격리도 포함된다.
VM과의 차이점
하지만, 컨테이너는 가상 머신이라고 부르는 VM과 달리 Host OS 위에서 별도의 가상화된 OS를 사용하지는 않고, Host OS에서 하나의 격리된 프로세스로써 직접 실행된다.
컨테이너의 장점
따라서 가상화로 인한 성능 오버헤드가 거의 없고, Host OS에서 직접 실행시킨 프로세스와 거의 유사한 성능을 보인다. 또한, VM 대비 작은 용량과 빠른 실행 속도를 제공한다.
컨테이너의 핵심 동작 원리
컨테이너는 가상 머신이 아니다. 단지, 실행 환경이 격리된 프로세스이다. 컨테이너를 블랙박스처럼 여기지 않고, 단순히 호스트 운영체제에서 실행된 격리된 프로세스로 생각하면 된다.
LXC(Linux Containers)는 단일 컨트롤 호스트 상에서 여러 개의 격리된 리눅스 시스템(컨테이너)들을 실행하기 위한 OS 레벨 가상화 방법이다.
운영체제로 보면, 컨테이너 이미지는 실행 파일, 컨테이너는 프로세스
리눅스 커널
- cgroups를 통해 프로세스별 자원 할당을 관리 (CPU, 메모리, 블록 I/O, 네트워크 등)
- namespace를 통해 장치를 격리해서 관리 (프로세스 트리, 네트워크, 사용자 ID, 마운트 된 파일 시스템 등)
가장 핵심적인 기능 : 파일 시스템의 격리, cgroups을 통한 성능의 격리, namespace를 통한 시스템 자원의 격리
파일 시스템의 격리
컨테이너 안에서 동작하는 애플리케이션이 자신만의 파일 시스템 공간을 가지도록 해준다. 이를 통해, 여러 개의 컨테이너가 동시에 동일한 호스트 머신에서 실행될 때, 각각의 애플리케이션이 서로의 파일 시스템을 침범하지 않고 독립적인 파일 시스템 환경에서 실행될 수 있게 된다.
cgroups을 통한 성능의 격리
리눅스 커널의 cgroup 기능 : 리눅스 커널에서 제공하는 기능
CPU, 메모리, 네트워크 대역폭과 같은 자원 사용량을 제한하거나 할당하는 기능이다. 이 때문에, 하나의 Host 머신에서 수십 개의 container가 동작해도 서로 성능 경합을 피하면서 자원 사용량을 효율적으로 관리할 수 있다.
namespace를 통한 시스템 자원의 격리
namespace : 리눅스에서 프로세스 간에 서로 다른 시스템 자원을 격리하는 기술
namespace는 각각의 컨테이너에게 고유한 시스템 자원을 제공하므로 컨테이너 안에서 실행되는 애플리케이션이 호스트 머신의 자원이나 다른 컨테이너의 자원을 침범하지 않도록 보호해 준다.
이러한 파일 시스템의 격리, cgroup을 통한 성능의 격리, namespace를 통한 시스템 자원의 격리 등의 기능을 통해 컨테이너는 가상화된 격리 환경에서 안전하고 효율적으로 애플리케이션을 실행할 수 있게 해 준다.
현재 컨테이너를 실행시키고 관리할 수 있게 하기 위해 사용하는 가장 잘 알려진 플랫폼은 도커이다.
Docker
- Docker : Linux, MacOS, Windows에서 동일한 방법으로 컨테이너를 실행시키고 관리할 수 있는 방법을 제공
- Dockerfile : 컨테이너 이미지를 자동으로 빌드할 수 있도록 지원하는 스크립트
- Docker Hub : 컨테이너 이미지를 저장하는 깃헙과 같은 컨테이너의 공개 저장소의 역할, 개발자는 이를 통해 빌드한 컨테이너 이미지를 쉽게 공유하고 배포할 수 있음.
- 기본적인 사용은 무료이지만, 일부 기능은 유료로 제공. 예를 들어, 한 개의 프라이빗 저장소는 무료로 만들 수 있지만, 그 이상은 비용을 지불.
- 규모가 크고 보안이 중요한 기업에서는 대부분 사내의 프라이빗 컨테이너 이미지 저장소를 별도로 구축하고 사용함. 이러한 프라이빗 컨테이너 이미지 저장소 또한, 컨테이너 이미지로 패키징 되어 있어서 컨테이너 실행만으로 쉽게 구축할 수 있음.
개발자는 docker에 docker 빌드 명령과 dockerfile을 통해 애플리케이션 실행 OS 환경의 종속성으로부터 독립된 소프트웨어 단위인 컨테이너 이미지로 패키징 할 수 있음.
docker
- OS 환경에 관계없이 애플리케이션에 일관된 런타임 환경을 제공하므로 로컬 개발, 테스트뿐만 아니라, 프로덕션 서버 환경을 비롯한 다양한 환경에서 애플리케이션을 더 쉽게 배포하고 관리할 수 있게 해 준다.
- 로컬에서 실행되는 컨테이너 이미지는 동일한 환경을 보장받은 채, 서버나 클라우드에서도 동일하게 실행된다. 때문에 docker를 사용하면, 개발한 앱을 컨테이너 이미지화 해서 배포 프로세스에 쉽게 자동화하고 필요에 따라 애플리케이션을 확장하는 동시에 호환성 문제와 다운 타임의 위험을 줄일 수 있다.
- 그리고, 빌드된 docker 컨테이너 이미지는 docker push 명령어를 통해 docker hub로 업로드할 수 있다. 공개된 저장소의 경우에는 docker pull 명령어를 통해 누구나 다운로드하여 그대로 실행시킬 수 있다.
컨테이너 오케스트레이터란 무엇이고 왜 필요한가?
서버 1대에서는 도커만으로 충분, 하지만 관리해야 할 서버가 수 천대라면?
컨테이너 오케스트레이터, 쿠버네티스를 사용하는 것이 좋다.
쿠버네티스의 주요 기능
대규모 분산 서버 환경에서 Container 관리를 자동화하는 유연하고 확장성 있는 오픈소스 플랫폼
- 쿠버네티스란 명칭은 키잡이(helmsman)이나 파일럿을 뜻하는 그리스어에서 유래
- k와 s 사이에 스펠링이 8개라서 k8s라고 불리기도 함. ex) i18n
- 컨테이너 배포, 자동 복구, 확장 등 컨테이너화된 애플리케이션의 관리를 위한 오픈소스 시스템
- 컨테이너 오케스트레이터의 사실상의 표준 기술, 크고, 빠르게 성장하는 생태계를 가짐
- Google Cloud GKE(Google Kubernetes Engine)
- AWS Elastic Container Service(ECS) → EKS(Elastic Kubernetes Service)
- MS Azure Container Service(ACS) → AKS(Azure Kubernetes Service)
쿠버네티스의 아키텍처
크게 Master node와 Worker node로 나뉜다.
각각 node에서 동작하는 Component들로 구성된다.
- Master node : Cluster의 제어를 담당하는 중심 node
- Worker node : 실제 Container가 동작하는 node
이러한 Master & Slave 아키텍처는 Jenkins에서도 많이 보았을 것이다. 실제 앱이 실행되는 모든 Worker node는 Master node를 바라보고 자신을 Worker로 등록하고 Master는 Worker node의 상태를 기준으로 적절한 명령을 실행시킨다.
Master node는 한 대의 서버로 구성할 수도 있으나, 이럴 경우, Master node 한 대의 장애 시 전체 Cluster에 대한 관리를 할 수 없게 된다. 따라서, 가용성을 위해 3대나 5대로 구성하는 것이 일반적이다.
- Master node를 3대로 구성 : 1대의 장애 시에도 정상적으로 유지
- 5대로 구성 : 2대의 장애까지 감내
- 가용성이 중요한 시스템인 경우 5대로 구성하고 1대의 장애 시 나머지 1대의 원인을 분석하고 재현을 해보는 등 문제를 해결한 뒤에 나머지 1대를 살리는 패턴으로 운영하는 것을 권장한다.
Master & Slave 아키텍처
- kube-apiserver : 모든 요청을 받고, etcd에 저장
- kube-controller-manager : 실제 Resource 관리를 수행하는 컨트롤러들의 집합
- kubelet : Worker Node의 에이전트로서 Node와 Pod의 상태 파악과 실행, 보고
- etcd : 모든 오브젝트의 상태를 저장하는 key/value 저장소
- kube-scheduler : 새 pod의 생성을 관찰하여 스케줄링
Worker Node에서 동작하는 컴포넌트
- kubelet : 워커 노드에 설치되는 에이전트(Master의 명령을 받음). kube-apiserver에 연결되어 마스터의 명령을 받아 컨테이너를 실행 및 관리. 마스터 노트에서 배포된 Pod의 스펙을 기반으로 Pod를 생성하고, 컨테이너의 상태를 모니터링하여 이상이 있는 경우 자동으로 복구해 주는 역할도 함.
- kube-proxy : Service 트래픽을 Pod로 연결. 모든 node에서 실행되며, 컨테이너로의 모든 연결을 지원. 일반적으로 iptables나 ipvs를 통해서 node의 트래픽 제어를 위한 룰 세팅을 해주는 에이전트의 역할을 수행하고 트래픽의 직접적인 처리는 운영체제의 kernel 기능을 통해서 처리됨. 서비스와 Pod를 연결하여 네트워크 트래픽을 전달할 수 있도록 한다.
- container-runtime : 컨테이너의 실행 환경. Master node에서는 사용자가 API를 호출하여 Cluster의 상태를 변경하고, 이에 대한 처리를 담당하는 Controller들이 동작. Worker node에서는 kubelet 에이전트가 명령을 받아 컨테이너 런타임을 통해 각각의 Pod를 실행하고 Container를 관리.
- ex) docker, containerd
이러한 아키텍처는 쿠버네티스가 Cluster 내의 여러 node에서 실행되는 컨테이너화된 애플리케이션을 효율적으로 관리하고 확장할 수 있는 강력한 플랫폼으로 자리 잡을 수 있게 한 핵심 요소 중 하나이다.
Declarative API(선언형 API) = Object와 Controller
쿠버네티스는 선언형 API를 사용한다. 이는 사용자가 Container 서비스 및 기타 리소스를 생성할 때 원하는 최종 상태를 정의하고 이를 Cluster에 제출하는 방식이다. 이후, 쿠버네티스가 이를 바탕으로 실제 상태와 사용자가 원하는 상태를 비교하여 필요한 변경 사항을 적용한다. 쿠버네티스에서는 json도 호환이 되지만, 주로 yml 형식으로 Manifest 파일에 작성하여 Cluster 리소스를 정의하고 배포한다.
쿠버네티스의 핵심 동작 원리
api Version
오브젝트 스키마의 버전. 컨트롤러는 버전별 호환성을 지원하기 위해 여러 가지 버전을 처리해야 한다. 이때 이 정보를 참조하여 버전별로 알맞은 동작을 수행한다.
kind
오브젝트의 종류이다. 개별 쿠버네티스 컨트롤러는 본인이 처리해야 할 타입의 오브젝트의 변화를 항상 감시하고 있다. 예를 들어 Deployment 컨트롤러는 etcd에 저장된 데이터들 중에서 kind가 Deployment 인 오브젝트의 업데이트에 대한 변화 이벤트만 항상 감시하고 있다가 적절한 동작을 실행한다.
metadata
해당 리소스 오브젝트에 대한 메타데이터를 저장하는 필드이다. 해당 리소스에 대한 이름과 네임 스테이스 정보 등이 관리되며 쿠버네티스에서 리소스 이름과 네임 스페이스의 조합은 항상 유니크하다. 따라서, 쿠버네티스 클러스터 내의 리소스를 구별할 때는 오브젝트의 종류와 이름, 네임 스페이스의 조합을 통해 구별할 수 있다.
spec
사용자가 원하는 리소스 상태이다.
status
쿠버네티스가 조사한 현재 리소스의 실제 상태이다.
Controller가 제출된 Object에 Desired State(=Spec)와 Current State를 비교, Resource 관리
쿠버네티스의 주요 오브젝트
- Pod : 쿠버네티스의 기본 배포 단위로 하나 이상의 컨테이너를 포함하는 컨테이너 그룹
- ReplicaSet : Pod의 복제본을 생성하고 관리
- Deployment : ReplicaSet을 관리하며, Pod의 롤링 업데이트나 롤백을 수행
- Service : Pod를 연결하고 서비스 디스커버리를 제공
- Ingress : 클러스터 외부에서 Service에 접근할 수 있도록 하는 API 오브젝트
- ConfigMap : 애플리케이션의 환경 설정 정보를 저장
- Secret : 애플리케이션의 보안 정보를 저장
- PersistentVolume : Pod에서 사용할 수 있는 스토리지를 관리
- StatefulSet : Pod를 개별적인 ID와 함께 관리
- DaemonSet : 모든 노드에 Pod를 실행하고 관리
- Cronjob : 스케줄링된 작업을 관리
- Job : 일회성 작업을 관리
Pod
Pod는 쿠버네티스에서 가장 작은 배포 단위, 하나 이상의 컨테이너를 포함하는 오브젝트임
파드는 언제나 노드 상에서 동작 (노드에서 실행되는 컨테이너로서 동작)
하나의 노드는 여러 개의 파드를 가질 수 있음
Pod 추상화를 통한 장점
- pause 컨테이너를 통해 namespace를 공유함으로서 Pod 내부의 컨테이너들은 Pod 외부와 격리된 컨테이너의 장점을 유지하면서, Pod 내부에서는 네트워크와 스토리지 자원을 공유
- 단일 컨테이너를 단일 목적으로 분리하기 더 쉬워짐
- Pos 안의 container는 주로 함께 동작하거나 배포되어야 하는 의존성이 밀접한 프로세스의 세트로 구성. 하나의 Pod에 여러 개의 container를 넣는 것은 관리의 용이성 때문
- 예를 들어, 웹 애플리케이션의 경우 container 1개는 nginx 와 같은 웹 서버를 담당하고, 다른 container는 웹 애플리케이션 자체를 담당. 또는, 로그 수집기인 Fluentd와 애플리케이션을 하나의 Pod로 구성하면 애플리케이션이 생성하는 로그를 로그 수집기가 받아서 로그 저장소로 전송.
이러한 패턴을 사이드 카 패턴이라고 함.
- Pod 안에 포함되는 컨테이너들은 같은 Pod 내부에서 네트워크와 스토리지를 공유할 수 있음. 따라서 하나의 Pod 안에 여러 개의 container를 배치할 때, 이들은 서로를 localhost로 호출할 수 있으며, 동일한 네트워크 네임스페이스와 스토리지를 사용할 수 있음. 이를 통해 컨테이너들이 단일 애플리케이션의 일부로써 밀접하게 상호 작용할 수 있게 됨.
- Pod는 Worker node의 사용자가 직접 배치하는 것이 아닌, 쿠버네티스에서 컨트롤러와 스케줄러에 의해 어떤 node에 배치할지를 결정. 사용자는 단지 Deployment나 StatefulSet, DaemonSet, Cronjob과 같은 Object들을 배포하기만 하면 쿠버네티스가 이들의 스펙을 보고 필요한 만큼의 Pod를 적절히 스케줄링해서 분산된 node에 배포함. 이를 통해 쿠버네티스에 의해 자동으로 애플리케이션의 가용성을 보장하고, 컨테이너의 생명 주기를 관리할 수 있음.
- Pod의 상태는 쿠버네티스에 의해 항상 감시됨. Pod가 종료되면, 쿠버네티스는 새로운 Pod를 만들어서 이전 Pod가 담당했던 일을 대신함. 이러한 방식으로 쿠버네티스는 Pod의 가용성을 항상 보장.
Deployment
애플리케이션 인스턴스를 생성하고 업데이트 하는 역할 담당
Deployment는 실제 애플리케이션 배포 시 사용.
Deployment는 내부적으로 ReplicaSet을 이용해서 Pod를 관리.
- ReplicaSet은 단순히 지정된 수의 Pod의 복제본을 유지하는 역할을 함
- Pod의 상태를 모니터링하여 Pod가 다운되거나 삭제된 경우에는 새로운 Pod를 생성하여 복제본의 개수를 항상 일정하게 유지하는 역할을 함.
- Deployment는 직접 Pod를 관리하지는 않지만, ReplicaSet 여러개를 이용하여 배포 및 롤백을 관리
- 예를 들어, Deployment에 정의된 Pod의 스펙 중에 Container 이미지 버전 Tag가 변경되면, Deployment는 롤링 업데이트를 진행하기 위해 새로운 버전의 ReplicaSet 오브젝트를 하나 생성함. 그리고 새로운 버전의 ReplicaSet의 Pod 개수를 늘리고, 구 버전의 ReplicaSet Pod 개수를 점진적으로 줄이는 방식으로 롤링 업데이트의 진행을 용이하게 관리함. 마침내 구 버전의 ReplicaSet의 Pod 개수가 0개가 되면, 롤링 업데이트가 끝나게 됨.
- 이러한 구조를 가지고 있기 때문에, 롤백은 더욱 쉽다. 신규버전 배포 진행과는 반대의 순서로 새로운 버전의 ReplicaSet의 Pod 개수를 다시 줄이고, 구 버전의 ReplicaSet의 Pod 개수를 다시 늘리는 방식으로 진행하면 되기 때문임.
이렇게 Deployment는 ReplicaSet이라는 중간 계층을 이용해 쉽게 애플리케이션을 배포하고 롤백하는데 사용함.
Service
Pod는 언제든 죽을 수 있는 존재이다. 따라서 pod IP를 그대로 사용하기는 어렵다.
이런 짧은 생명 주기의 pod IP를 사용하지 않고, 고정된 단일 엔드포인트를 제공하자.
이렇게 되면 Service 여러 개의 pod를 하나의 엔드포인트 접근할 수 있도록 해준다.
서비스의 4가지 Type
- ClusterIP (default) : 이것이 디폴트 타입. ClusterIP는 클러스터 내부에서만 사용 가능하며, 클러스터 내 Pod나 Node에서만 접근 가능
- NodePort : 이것은 클러스터의 모든 노드에 특정 포트를 해당 서비스로의 연결로 노출시키는 방법. 예를 들어, NodePort를 선언하고 35000번 포트를 할당 받았다면, 클러스터 내 모든 노드의 35000번 포트로 접근하면 연결된 Pod로 로드밸런싱 해줌.
- LoadBalancer : 클라우드 서비스 제공업체의 로드밸런서를 이용하여 외부에서 접근할 수 있는 External IP를 부여받을 수 있음.
- External Name : External Name은 외부 도메인 주소를 쿠버네티스 클러스터 안에서 고정된 서비스의 네임으로 사용하기 위해서 사용.
서비스의 IP는 서비스의 Name이 자동으로 클러스터 DNS에 등록되므로 클러스터 내부에서는 서비스 Name만으로 고정된 엔드포인트의 통신이 가능함.
서비스를 생성하면 생기는 단일 엔트포인트인 IP는 2가지로 나눌 수 있음
- Cluster IP : 클러스터 내부에서만 사용 가능 (pod / node 안에서만 가능)
- External IP : 클러스터 외부에서 사용 가능 (LoadBalancer Type 서비스)
Deployment, Service, Ingress 간의 관계
실제 유저가 애플리케이션을 배포하고 엔드포인트를 노출시키기 위해서 배포해야 하는 주요 오브젝트는 Deployment, Service, Ingress 이다. 이들의 기본적인 yml의 형태는 위의 그림과 같다.
먼저 Deployment를 통해 배포된 애플리케이션은 label selector를 통해 key와 value 쌍이 같은 경우 서비스로 연결되어 트래픽이 분산처리를 받게 된다. 서비스에 연결된 Ingress는 서비스의 name으로 매핑되어, 노출되게 된다. 해당 서비스의 도메인 정보나 SSL 인증 설정은 Ingress를 통해 처리되게 된다.
쿠버네티스에서의 서비스 트래픽의 흐름
사용자의 앱 하드 : L7 로드 밸런서의 역할을 하는 Ingress Controller와 L4 로드 밸런서의 역할을 하는 서비스에 의해 노출.
Ingress를 통해 유입된 사용자의 요청 트래픽은 연결되어져 있는 서비스에 따라 앱 하드들에게 적절히 분산됨.
2023 KAKAO Tech Campus_BackEnd 2단계
"카카오 클라우드 필수 강의"를 정리한 글입니다.
'개발 > 클라우드' 카테고리의 다른 글
클라우드 보안 - 취약성 : 데이터 유출(Data Breach) (0) | 2023.07.09 |
---|---|
클라우드 보안 - Zero Trust 모델 (0) | 2023.07.09 |
클라우드 네이티브 애플리케이션 개발 (0) | 2023.07.07 |
IT 서비스와 클라우드 (0) | 2023.07.06 |
클라우드 서비스 AWS (1) (0) | 2023.05.29 |