Pod


Pod 는 하나 이상의 컨테이너를 실행하기 위한 쿠버네티스의 최소 단위이다.

  • 전체 클러스터에서 고유한 IP 를 할당받는다.
  • 1~N 개의 Container 를 포함
    • 일반적으로 파드는 애플리케이션을 실행하는 컨테이너와 1:1 관계를 갖는다.
    • 하나의 파드는 여러 컨테이너를 포함할 수 있으며, 동일한 종류의 컨테이너를 복제하기보다는 헬퍼(보조) 컨테이너를 함께 두는 경우가 많다.
  • host 폴더 공유, localhost 네트워크 공유
    • 이는 앱 컨테이너와 헬퍼 컨테이너가 localhost를 통해 통신할 수 있음을 의미한다.

How to deploy pods

kubectl run nginx --image nginx
kubectl get pods
  • 첫 번째 명령은 Kubernetes가 구성된 레지스트리(공개 Docker Hub 또는 private registries 등)에서 이미지를 가져온다.

YAML in K8s

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  labels:
    app: myapp
    type: front-end
spec:
  containers:
    - name: nginx-container
      image: nginx

Kubernetes는 오브젝트 생성을 위해 YAML 파일을 입력으로 사용하며, 모든 오브젝트는 4개의 최상위 필드를 갖는다.

  • apiVersion: 오브젝트 생성을 위해 사용하는 Kubernetes API 버전
    • e.g. v1, apps/v1
  • kind: 생성할 오브젝트의 종류
    • e.g. Pod, Service, ReplicaSet, Deployment
  • metadata: 오브젝트를 설명하는 메타데이터(사전 형태)
    • e.g. name, labels
  • spec: 생성할 오브젝트의 구체적인 정의
    • 위 예시에서 containers 섹션은 하이픈(-)으로 나열되는 리스트다.

아래 명령으로 파드를 생성할 수 있다:

kubectl run nginx --image=nginx

또는 YAML 파일을 사용한다:

kubectl create -f pod-definition.yaml
kubectl get pods
kubectl describe pod myapp-pod
kubectl run redis --image=redis --dry-run=client -o yaml > redis.yaml

kubectl 명령어로 template yaml file 을 생성하고 싶은 경우 --dry-run-o yaml 옵션을 통해 yaml file 을 생성할 수 있다.

파드와 컨테이너는 결국 리눅스 프로세스다

  • 컨테이너/파드가 결국 namespace, cgroup 등으로 격리된 리눅스 프로세스라는 일반 개념은 Containers are just Processes 참조
  • 파드는 하나 이상의 컨테이너(프로세스 집합)를 묶은 논리적 단위일 뿐, 실제로는 같은 namespace/network/storage 를 공유하는 리눅스 프로세스들의 집합이다
  • 즉, 쿠버네티스에서 컨테이너/파드를 생성하면 결국 리눅스 커널이 namespace 와 cgroup 을 활용해 프로세스를 격리하고 관리하는 것에 불과하다

ReplicaSet


ReplicaSet 은 특정 Pod 템플릿을 기준으로 항상 N개의 Pod 를 유지하도록 보장하는 리소스이다.

  • Pod 를 생성/삭제하여 클러스터 내 원하는 개수의 Pod 를 유지함으로써 고가용성을 제공한다.
  • 기존 또는 추가 노드에 Pod 를 증설하여 로드밸런싱과 스케일링을 지원한다.
  • ReplicaSet 은 클러스터의 여러 노드에 걸쳐 동작한다.

Creating ReplicaSet with YAML

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: myapp-replicaset
  labels:
    app: myapp
    type: front-end
spec:
  template: # ReplicaSet 이 관리할 Pod 정의
    metadata:
      name: myapp-pod
      labels:
        app: myapp
        type: front-end
    spec:
      containers:
      - name: nginx-container
        image: nginx
  replicas: 3 # Pod 복제 수
  selector: # ReplicaSet 이 관리할 Pod 식별자
    matchLabels:
      type: front-end

아래 명령으로 생성하고 결과를 확인한다.

kubectl create -f replicaset-definition.yaml
kubectl get replicaset
kubectl get pods

파일을 수정해 스케일링:

kubectl replace -f replicaset-definition.yaml

명령으로 스케일링:

kubectl scale --replicas=6 -f replicaset-definition.yaml

또는

kubectl scale --replicas=6 replicaset myapp-replicaset
  • 이 두 명령은 정의 파일 자체를 변경하지 않는다.

Deployment


Deployment 는 ReplicaSet 을 관리하는 상위 리소스이다.

  • 내부적으로 ReplicaSet 을 이용하여 배포 버전을 관리한다.
  • 이미지 변경 시 자동으로 새 ReplicaSet 을 생성하고 롤링 업데이트를 수행한다.
  • 실패 시 롤백도 자동으로 지원한다.

Creating Deployment with YAML

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-replicaset
  labels:
    app: myapp
    type: front-end
spec:
  template: # (required)
    metadata:
      name: myapp-pod
      labels:
        app: myapp
        type: front-end
    spec:
      containers:
      - name: nginx-container
        image: nginx
  replicas: 3 # default 값은 1
  selector: # (required)
    matchLabels:
      type: front-end
  • Deployment 정의는 ReplicaSet 정의와 사실상 동일하다.

아래 명령으로 생성하고 생성된 오브젝트를 확인한다:

kubectl create -f deployment-definition.yaml
kubectl get deployments
kubectl get replicaset
kubectl get pods

또는

kubectl get all

Service


Service 는 여러 개의 Pod 로 구성된 애플리케이션을 하나의 네트워크 엔드포인트로 외부에 노출시키는 리소스이다.

  • Pod 는 언제든지 재시작되거나 재배포될 수 있기 때문에 IP 가 항상 바뀔 수 있다.
  • Service 는 클러스터 내부에 고정된 가상 IP 를 외부에 노출시켜 클라이언트가 항상 같은 IP 주소로 파드에 접근할 수 있도록 한다.
  • Label Selector 를 이용해 트래픽을 전달해야할 Pod 를 지정할 수 있다.
  • 내부적으로 각 노드에서 실행되는 kube-proxy 가 Service 의 가상 IP 로 들어온 요청을 Label Selector 조건에 해당하는 Pod 중 하나로 전달한다.
  • 여러가지 Service Type 을 가지고 있다.
    • ClusterIP
    • NodePort
    • LoadBalancer 등

Service 흐름

클러스터 내부에서 Service 를 호출할 때:

  1. 클라이언트가 Service 의 ClusterIP:Port 로 요청을 보낸다.
  2. kube-proxy 가 이 요청을 가로챈다.
  3. EndpointSlice 를 참조해 적절한 Pod IP 로 라우팅한다.
  4. Pod 는 targetPort 에서 요청을 받는다.

아래 ClusterIP 예시로 들면, back-end:8080 으로 들어온 요청이 label 이 app=myapp, type=back-end 인 Pod 의 80 포트로 전달된다.

ClusterIP

apiVersion: v1
kind: Service
metadata:
  name: back-end
spec:
  type: ClusterIP # default 값
  selector: # label 기준으로 트래픽을 보낼 pod 결정
    app: myapp
    type: back-end
  ports:
   - port: 8080 # (required) 클라이언트가 Service 에 요청을 보낼 port
     targetPort: 80 # 실제 pod 에서 리스닝하는 port, 지정하지 않으면 port 값을 따름
  • ClusterIP 는 클러스터 내부에서만 접근 가능한 IP 로 기본값으로 적용된다.
  • 클러스터 내부에서 서비스 연결은 DNS 를 이용
  • 가령 3-tier-architecture 에서 backend app 에 요청을 보내고 싶을 때 backend 를 담당하는 여러 Pod 가 cluster 전체에 퍼져있으니 ClusterIP 를 통해 원하는 layer 를 지정

NodePort

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  type: NodePort
  selector: # label 기준으로 트래픽을 보낼 pod 결정
    app: myapp
    type: front-end
  ports:
   - port: 80 # (required) 클라이언트가 Service 에 요청을 보낼 port
     targetPort: 80 # 실제 pod 에서 리스닝하는 port, 지정하지 않으면 port 값을 따름
     nodePort: 30008 # 30000-32767 범위, 지정하지 않을 경우 자동으로 할당됨
  • 모든 노드에 동일한 고정 포트(30000-32767)를 생성하여 외부에 노출한다.
  • 외부에서 {Node IP}:{nodePort} 형식으로 접근할 수 있다.
  • NodePort 역시 ClusterIP 를 가지기 때문에 내부적으로는 ClusterIP 를 통해 Pod 에 접근한다.
kubectl run httpd --image=httpd --port=80 --expose
  • kubectl run 으로 Pod 를 생성할 때 —expose 옵션을 주면 Pod 에 연결되는 ClusterIP 를 자동으로 생성해준다. ClusterIP 와 연결되어야 하기 때문에 —port 옵션이 필수적으로 포함되어야 한다.

LoadBalancer

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  type: LoadBalancer
  ports:
   - port: 80
     targetPort: 80
     nodePort: 30008
  • AWS, Azure, GCP 등에서 제공하는 Native Load Balancer 로 부하를 분산하여 보내는 역할을 수행한다.
  • 내부적으로 NodePort 기반으로 작동한다.

ExternalName

apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  type: ExternalName
  externalName: myapp-service.example.com
  • 외부 DNS 이름으로 라우팅 CNAME 처럼 작동한다.
  • 프록시가 아닌 DNS 레벨에서 리다이렉션

Namespaces


K8s Cluster 를 구축하게되면 기본적으로 Default, kube-system, kube-public Namespace 들이 자동으로 생성된다. 사용자가 생성한 Pod, Deployment, Service 등 K8s Object 들은 기본적으로 Default Namespace 에 속하게 된다.

kubectl get pods --namespace=kube-system

kube-system Namespace 의 경우, coredns, etcd-master, kube-apiserver-master, kube-controller-manager-master 등 K8s 의 기본적인 Component 들을 확인할 수 있다.

kubectl create -f pod-definition.yml --namespace=dev

K8s Object 를 특정 Namespace 에 배포하고 싶을 경우엔 옵션으로 namespace 를 지정해주거나,

apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  namespace: dev
  labels:
    app: myapp
    type: front-end
spec:
  containers:
    - name: nginx-container
      image: nginx

위 처럼 metadata 필드에 namespace 를 추가해주면된다.

apiVersion: v1
kind: Namespace
metadata:
  name: dev

Namespace 를 생성할 땐 위처럼 Namespace yaml 을 생성한 뒤,

kubectl create -f namespace-dev.yml

위 명령어로 생성하거나,

kubectl create namespace dev

yaml 파일 없이 명령어로 생성할 수 있다.

kubectl config set-context $(kubectl config current-context) --namespace=dev

위 명령어를 통해 cli 작업 위치를 특정 Namespace 로 이동시킬 수도 있다.

apiVersion: v1
kind: ResourceQuota
metadata:
  name: compute-quota
  namespace: dev
spec:
  hard:
    pods: "10"
    requests.cpu: "4"
    requests.memory: 5Gi
    limits.cpu: "10"
    limits.memory: 10Gi

특정 Namespace 에 하드웨어 리소스를 할당할 때엔 ResourceQuota yaml 을 생성한 뒤,

kubectl create -f compute-quota.yml

생성하여 자원을 할당해줄 수 있다.

Imperative vs Declarative


IaC 에선 명령형과 선언형 방식으로 인프라를 구축할 수 있는데,

kubectl run --image=nginx nginx
kubectl create deployment --image=nginx nginx
kubectl expose deployment nginx --port 80

위 같은 명령어들을 통해 Object 를 생성하고,

kubectl edit deployment nginx
kubectl scale deployment nginx --replicas=5
kubectl set image deployment nginx nginx=nginx:1.18

생성된 Object 를 수정할 수 있다. 이는 명령형 방식으로 K8s 를 구축하는 방법이다.

kubectl create -f nginx.yaml
kubectl edit deployment nginx

K8s configuration file 을 통해 Object 를 생성한 경우 선언형 방식으로 구축했다고 생각할 수 있지만, edit 명령어를 통해 접근한 file 은 K8s memory 에 동적으로 생성된 yaml 인 것을 확인할 수 있다. 때문에 edit 으로 설정을 바꾼다고 기존에 작성한 nginx.yaml 은 수정되지 않기 때문에 추후에 replace 등을 할 경우 예상하지 못한 변경이 생길 수 있다. 이를 방지하기 위해선 edit 대신 configuration file 자체를 변경한 후 replace 를 실행하는 것이 바람직하다.

kubectl apply -f nginx.yaml

K8s 는 apply 를 통해 선언형 방식으로 Object 를 제어할 수 있다. apply 는 이미 만들어진 Object 를 파악하며 부분적으로 설정이 수정된 경우 해당 부분만 적용하는 기능 역시 지원한다.

# Live object configuration
apiVersion: v1
kind: Pod
metadata:
  name: myapp-pod
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: {"apiVersion": "v1", ...}
  labels:
    app: myapp
    type: front-end
spec:
  containers:
  - name: nginx-container
    image: nginx
status:
  conditions:
  - lastProbeTime: null
    ...

kubectl apply 를 실행하면 K8s 는 사용자가 작성한 yaml file 과 K8s memory 에 저장된 Live object configuration 과 kubectl.kubernetes.io/last-applied-configuration 필드를 비교하여 변경된 사항만 부분적으로 확인하고 적용이 가능하다.

References