Kubernetes

Kubernetes ? (1)

id1112 2025. 4. 14. 11:34

Container

컨테이너는 애플리케이션과 이를 동작시키는 데 필요한 모든 환경 ( 라이브러리, 설정, 등)을 하나로 묶은 패키지이다.

개발자가 만든 프로그램을 어디서든 똑같이 실행할 수 있도록 도와주는 기술이다.

 

Docker 로 여러 개의 컨테이너를 다루게 되면 생기는 문제

1) 컨테이너 간 통신 문제 (Networking)

웹 서버, 데이터베이스 Redis 등 각각 컨테이너를 서로 연결해야한다.

하지만 이때 IP 주소가 바뀌거나 포트가 겹치게 될 수도 있고, 컨테이너가 종료되고 다시 실행되는 경우 주소가 새로 생성되기 때문에 찾을 수 없다.

 

2) 배포 복잡성

컨테이너의 갯수가 늘어날 수록 실행 분서와 배포해야하는 서버 그리고 실행 여부 등을 확인하고 처리하는 데 어렵다.

 

3) 상태 관리 & 복구

컨테이너의 현재 상태를 확인할 수 있어야하고 이를 자동으로 복구 및 확장 시킬 수 있어야함

 

4) 보안 & 설정 관리

환경 변수, 비밀번호와 같은 민감한 정보를 관리하고 보안 차원에서의 처리가 필요하다.

 

5) 모니터링 & 로깅

컨테이너가 늘어날 수록 전체적인 로그가 많아지고 문제가 발생했을 때 어느 컨테이너가 문제인지 추적 자체가 어려워진다.

 

 

Container vs VM

둘다 모두 애플리케이션을 독립적인 환경에서 실행하기 위한 기술이지만 이 둘의 구조는 전혀 다르다.

VM - 물리적 서버 위에서 Hypervisor를 설치하고 그 위에 각기 다른 OS 와 Application 을 올리는 방식이다. 각 VM은 자체 OS를 포함하기 때문에 무겁고, 실행 속도가 느리며, 자원적인 소모가 많다.

Hypervisor는 물리 서버 자원을 분한하여 각 VM에게 제공하며 VM은 해당 자원을 바탕으로 완전한 OS 환경을 갖추고 동작한다

반대로 

Container 는 호스트 OS의 Kernel을 공유하면서 애플리케이션과 이에 필요한 라이브러리, 설정만 포함된 독립적인 환경을 생성한다. 별도의 OS를 포함하지 않기 때문에 매우 가볍고 빠르게 실행된다.

컨테이너는 OS 수준의 가상화로 Linux Kernel의 namespace 와 cgroups(control groups) 기능을 이용해 서로 격리된 환경을 제공한다. 이를 통해 같은 OS 내에서 격리된 애플리케이션이 여러 개 동시에 실행될 수 있다.

 

컨테이너는 부팅 속도가 매우 빠르다. 보통 몇 초 내로 실행이 가능하며 메모리와 디스크 사용량이 작아 수십 개 수백 개도 효율적으로 운영할 수 있다. 반면 VM은 OS를 포함하기 때문에 수 분이 걸리는 경우도 있으며 실행 시 메모리와 디스크를 상당히 많이 소모한다. 이러한 차이 때문에 컨테이너는 경량화, 빠른 배포, 자동화, 마이크로서비스 아키텍처에 최적화되어있다.

반면 VM은 보안성이 더높고, 다양한 OS를 동시에 실행할 수 있으며, 레거시 시스템을 유지하는 데 적합하다.

 

보안 측면에서 VM은 Hypervisor 수준에서 완전히 독립된 OS를 사용하므로 한 VM 이 보안이 해킹당해도 다른 VM에는 영향을 주지 않는다. 반면 Container는 Kernel을 공유하기 때문에 이론적으로 Kernel 취약점이 있으면 전체 시스템이 뚫릴 가능성이 존재한다.

컨테이너는 빠른 배포가 필요한 Microservice, CI/CD pipline, Serverless Computing, Cloud Native App 에 주로 사용된다.

Kubernetes, Docker Swarm 과 같은 Orchestration tool 과 잘 통합된다.

VM은 다양한 OS를 테스트하거나 보안이 중요한 시스템, 기존 레거시 애플리케이션을 실행할 때 유용하다. 클라우드 서비스도 대부분 기본적으로 VM 기반이다. 

 

실제 많은 기업 환경에서 VM 위에 컨테이너를 실행하는 방식을 사용한다. 예를 들어 AWS EC2 위에 Linux 를 기반으로 사용할 때 그 위에 Docker Container 를 실행하는 방식이 일반적이다. 이 방식은 VM 보안성과 Container 의 민첩성 장점을 모두 가질 수 있는 구조이다.

이처럼 둘은 서로 대체 관계가 아니라 상호 보안적으로 활용될 수 있는 기술이다.

 

차이점

1)  커널 공유 vs 커널 독립

가장 대표적인 차이점 중에 하나로 컨테이너는 Host의 Kernel을 공유하기 때문에 Kernel 수준에서 어떤 기능을 사용할 때 제한이 발생할 수 있다. 예를 들어 컨테이너 안에서 mount, sysctl 같은 시스템 콜이 발생했을 때 제한이 될 수 있다.

반면 VM 은 자체적으로 Kernel이 존재하기 때문에 완전하게 Kernel 기능을 제어 가능하다.

= 커널 수준 기능을 직접 다루는 운영체제 수준의 개발이나 실험 환경 구성에선 VM이 더 적합하다.

 

2) Device 접근 제어 (Device Passthrough)

VM은 GPU, USB, Network 장비 등을 Hypervisor 수준에서 직접 Passthrough로 연결한다.

예를 들어 GPU를 VM에 독점적으로 할당하여 고성능 연산수행 (ML/ AI 작업) 등이 가능하다.

하지만 Container은 Kernel 을 공유하기 때문에 기본적으로 전체 디바이스에 접근하지 못한다.

물론 특수한 설정을 통해 Host Device를 공유할 수 있지만 보안적으로 매우 주의해야한다.

= 하드웨어 자원을 직접 다뤄야 할 때는 VM이 안정성과 성능에서 더 유리하다

 

3) 네트워크 구성의 복잡도와 유연성

VM은 기본적으로 자체 가상 NIC를 할당받아 독립적인 Network Interface를 가지며 DHCP, Routing 등을 VM마다 설정이 가능하다.

컨테이너는 일반적으로 Bridge Network, Overlay, NAT를 사용하며, 네트워크 모델 자체가 Linux Kernel Network Stack 기반이다. 컨테이너 간 통신은 Host IP + Port Forwarding으로 접근하거나 Kubernetes Network Plugin (CNI) 구성이 필요하다.

=복잡한 네트워크 토폴로지, 방화벽, VLAN 등을 정교하게 구성해야 할 땐 VM 쪽이 편리할 수 있다.

 

4) 보안 격리 수준

VM은 하드웨어 가상화 기반으로 보안 경계가 매우 강력하다. 한 VM 이 둟려도 Hypervisor와 다른 VM은 안전한 편이다.

반면 Container은 같은 Kernel 을 공유하기 때문에 Kernel 취약점이 전체 시스템에 영향을 줄 수 있다.

= 고신뢰 보안이 필요한 환경 (예: 금융, 군사, 다중 테넌시 클라우드)에서는 VM이 기본 선택이고, 컨테이너는 샌드박스 조합으로 가능성 확보 중이다.

 

5) 운영 및 관리 자동화 관점

컨테이너는 이미지 기반의 선언적 관리가 쉽다.

한 줄의 Dockerfile 한 개의 YAML 로 전체 앱을 정의할 수 있다

immutable image, layer 캐싱, CI / CD 와 결합이 편리하다

반대로 VM은 전체 OS를 운영해야하기 때문에 패치, 보안 업데이트, 버전 관리가 더 무겁고 복잡하다. 그래서 OS 수준의 관리 Tool 등을 별도로 활용해야한다.

= 자동화와 DevOps 최적화 환경에서는 Container가 휠씬 유리하다.

 

6) 스냅샷 & 복원 / 이식성 차이

VM은 전체 상태 (OS + 앱)을 이미지 또는 스냅샷으로 만들어 백업 및 복원이 가능하다 하지만 단점은 너무 무겁다는 점이다.

Container는 앱만 포함하는 이미지이기 때문에 이식성이 높고 여러 플랫폼에서 쉽게 실행이 가능하다는 장점이 있다.

단 Container는 Runtime 상태 (메모리 등) 스냅샷 복원이 상대적으로 복잡한 편이다.

VM은 완전 복원, 디버깅, 고정 테스트 환경 등에 유리하다

컨테이너는 빌드 -> Test -> Deply 이식성에 최적화되어 있다.

 

 

Orchestration ?

여러 개의 컨테이너나 서비스들이 정해진 순서와 방식대로 잘 협력하면서 작동하도록 자동으로 조율하는 시스템을 말한다.

여러 개의 컨테이너를 정확한 순서로 배포되고, 통신하고, 자동 복구하고, 확장되도록 하기 위해서 사용된다.

 

Orchestration 이 해주는 일

1. Deployment (자동 배포)

코드를 빌드하고 컨테이너를 생성하고 Node에 배포할 수 있다.

2. Scaling (스케일링)

트래픽이 많아지면 자동으로 인스턴스를 늘리고 반대로 줄일 수 있다.

3. Service Discovery & Load Balancing

컨테이너들을 자동으로 찾고 통신할 수 있다.

4. Self - Healing

문제가 생긴 컨테이너를 자동으로 다시 시작하거나 교체할 수 있다.

5. Update 및 Rollback

새 버전을 적용하고 문제가 생기면 자동으로 되돌릴 수 있다.

6. Secrets & Configs

환경 변수나 민감한 정보들을 안전하게 관리할 수 있다.

 

이러한 Orchestration 에 대표적으로 사용되는 Tool 이 Kubernetes 인데 

과연 Kubernetes는 어떻게 컨테이너를 사용하게 해주는가?

Kubernetes 가 하는 일의 핵심은 자동화이다. 사람이 일일이 관리하지 않아도 쿠버테니스가 컨테이너의 배포, 실행, 스케일링, 복구, 업데이트 등을 자동으로 사용할 수 있게 도와준다.

 

Kubernetes 의 자동화

1. Deployment Automation

  kubectl apply -f deployment.yaml 를 실행하면 컨테이너 여러 개가 원하는 개수만큼 배포가 된다. 

# 예: deployment.yaml
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: my-app
          image: myapp:v1

- kubectl apply 를 하면 Kubernetes 가 current state를 확인한다.

- YAML 에 적힌 desired state 과 비교한다.

- 차이가 있으면 자동으로 수정해서 current state와 desired state를 동일하게 맞춘다.

파드가 없으면 새로 생성하고 이미지가 변경되었으면 Rolling Update 를 실행한다. 기존 파드를 하나씩 죽이고 새로운 이미지로 대체한다.

 

2. Self Healing

컨테이너가 죽거나 에러가 나면 Kubernetes가 자동으로 재시작을 하거나 다시 생성한다

이 때 Health Check 는 probes 로 설정이 가능한다.

livenessProbe:
  httpGet:
    path: /healthz
    port: 8080

Probes 는 Kubernets 에게 해당 컨테이너가 정상적으로 작동하는 지에 대한 기준을 알려줘야하는 데 해당 설정을 하는 것이 probe이다.

probes의 3가지 역할은 위와 같다. 그리고 자세한 내용은 밑에서 다루겠다.

 

3. Auto - Scaling

트래픽이 많아지면 컨테이너 수를 자동으로 증가시키고 반대로 줄어들면 컨테이너의 수도 자동으로 줄인다.

Horizontal Pod Autoscaler(HPA) 기능 

kubectl autoscale deployment my-app --min=3 --max=10 --cpu-percent=50

HPA는 Kubernetes에서 제공하는 Auto Scaling 기능 중 하나로 Pod 개수를 상황에 따라 자동으로 늘리고 줄이는 기능이다.

이때 주로 CPU 사용량 도는 그 외의 메트릭을 기준으로 Pod들의 리소스 사용 상태를 모니터링하고 해당 사용량이 기준치를 초과하면 Pod의 수를 늘리고 기준치보다 한참 낮아지게 되면 Pod의 수를 줄이는 방식이다.

kubectl autoscale .. 명령어를 사용해서 특정 Deployment 또는 ReplicaSet, ReplicationController 등에 HPA를 설정하고 최소/ 최대 Pod 개수와 트리거 (예를 들어 CPU Percent)를 지정할 수 있다.

 

4. Rolling Update & Rollback

Rolling Update는 기존 버전을 전부 내린 뒤 새 버전을 배포하는 것이 아니라 Pod를 하나씩 차례대로 교체하면서 새로운 버전을 점진적으로 배포하는 방식이다. 이를 통해 서비스 중단 시간을 최소화하고 문제가 생겼을 때 Roll Back 기능을 통해 이전 버전을 빠르게 되돌릴 수 있다.

동작 방식

1) 새로운 버전의 Container(image)로 Update Plan 을 세운다.

2) 배포 시 Deployment(또는 ReplicaSet 등)에 설정된 전략(strategy)에 따라

하나의 pod 또는 설정된 개수를 먼저 종료한다. (또는 준비 상태로 만들어서 비활성화시킨다)

새로운 버전의 Pod를 생성해 준비가 완료되는 지를 확인한다.

새 Pod가 정상적으로 동작하면 예전 pod를종료하고 새 pod를 생성하는 과정을 반복한다.

모든 구 버전 pod가 새로운 버전으로 교체될 때까지 위 과정을 순차적으로 진행한다.

 

서비스 중단 최소화

- maxUnavailable, maxSurge 와 같은 파라미터를 통해

동시에 몇 개의 Pod를 내려도 되는 지

새 Pod를 몇 개까지 미리 띄울 수 있는 지 등을 설정할 수 있다

- readinessProbe, livenessProbe 등 

healthcheck를 통해 새 버전 Pod가 정상적으로 응답하는 지 확인해 문제가 있으면 배포 속드를 늦추거나 중단할 수 있다.

 

Rollback 은 Deployment 등 Kubernetes Resource가 Revision History를 통해 버전 이력을 관리한다.

kubectl rollout undo 명령어 등을 사용해 특정 버전을 즉시 되돌릴 수 있다.

readinessProbe가 지속적으로 실패하는 등 특정 조건이 감지되면 자동으로 롤백이 되도록 구성할 수 있다.

(kubernetes 기본 기능만으로는 자동 Rollback 을 구성하는 것이 제한적이고 Arogo Rollouts 등 추가적인 도구를 사용하는 경우가 많다.)

 

5. Service Discovery & Load Balancing 자동화

Pod는 장애, 스케일림, rolling update 등으로 수명이 짧고 IP가 자주 변경된다. 직접 Pod의 IP를 사용해 통신하면 IP가 변경될때 마다 설정을 수정해야하는 문제가 발생한다. 해당 문제를 해결하기 위해서 Kubernetes는 Service Object를 제공한다. Service는 여러 Pod 를 하나의 논리적인 Endpoint로 묶어서 외부나 클러스트 내부의 다른 서비스가 접근할 수 있도록 해준다.

Pod들이 IP 계속 바뀌더라도 Service 가 중간에서 자동으로 연결해준다. (DNS 이름으로 접근이 가능하다.)

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

 

 


Cluster 구조

Cluster
 ├── Namespace
 │    ├── Deployment
 │    │    ├── ReplicaSet
 │    │    │    ├── Pod
 │    │    │    │    └── Container
 │    │    └── ...
 │    ├── Service
 │    └── ConfigMap / Secret
 └── Node
       ├── kubelet
       ├── container runtime
       └── Pod

 

Namespace

Namespace는 Kubernetes Cluster 안에서 리소스들을 논리적으로 구분하는 방법이다. Kubernetes는 하나의 클러스터 안에서 수많은 리소스(Pod, Service, Deployment 등)를 실행하고 관리하게 된다. 그런데 만약 이 모든 리소스가 하나의 공간 안에서만 존재한다면, 서로 이름이 겹치거나 관리가 복잡해지는 문제가 생긴다. 이러한 문제를 해결하기 위해 쿠버네티스는 Namespace라는 논리적인 공간을 제공한다.

Namespace를 사용하면 Cluster 내의 리소스를 그룹으로 묶어서 관리할 수 있다. 이를 통해 서로 다른 프로젝트, 팀 그리고 환경의 리소스들의 이름을 충돌 없이 관리 할 수 있고 관리 포인트를 나눌 수 있게 된다.

Kubernetes Resource는 생성될 때 어떤 Namespace에 속할 지 지정할 수 있다. 하지만 별도로 지정하지 않으면 기본적으로 default 라는 Namespace에 속하게 된다. 

관리자는 필요에 따라 새로운 Namespace를 생성할 수 있으며 각 Namespace는 별도의 리소스를 배포하거나 조회 및 삭제할 수 있다.

kubectl apply -f my-pod.yaml -n dev
kubectl get pods -n dev

해당 명령어는 dev 라는 Namespace 안에 Pod를 배포한다.

명령어를 통해배포하는 방법이외에도 

#yaml file 
metadata:
  name: my-app
  namespace: dev

YAML 파일에 직접 명시할 수 있다.

 

Namespace는 완벽한 보안 격리 수단은 아니다. 그래서 RBAC, ResourceQuota, NetworkPolicy 등과 함게 사용하여 안전하고 독립적인 환경을 구성하려고 한다.

 

Kubernetes 에 기본적으로 존재하는 Namespace

  • default: 사용자가 따로 Namespace를 지정하지 않을 경우 사용되는 기본 공간
  • kube-system: 쿠버네티스 내부 시스템(Pod, DNS, etcd 등)이 동작하는 공간
  • kube-public: 모든 사용자가 읽을 수 있는 공용 정보 저장용 (거의 사용 안 함)
  • kube-node-lease: 노드 상태 정보(heartbeat)를 빠르게 확인하기 위해 사용됨