Docker

kubernetes 시작하기

쿠와와 2021. 1. 29. 13:26

기본적인 구조


모든 리소스는 오브젝트 형태로 관리된다. 

 

컨테이너의 집합 = Pods

집합을 관리하는 컨트롤러 = Replica Set

사용자 = Service Acoount 

노드 = Node

하나의 오브젝트로 사용할 수 있음 

 

# 사용할 수 있는 오브젝트 확인
kubectl api-resources 

# 특정 오브젝트의 간단한 설명
kubectl explian 오브젝트_이름 

 

전부다 다루지는 않고 자주 사용하는 것 위주로 설명하겠다. 

 

쿠버네티스는 명령어로도 사용할 수 있지만, YAML 파일을 더 많이 사용한다. 

컨테이너, 설정값, 비밀값등 모든 것을 YAML 파일로 작성하기 때문에 쿠버네티스를 잘 사용한다는 것은 YAML 파일을 잘 작성하는 것이다. 

 

쿠버네티스는 여러 개의 컴포넌드로 구성돼 있다. 

크게 마스터 노드와 워커 노드로 나눠어져 있다. (관리와 애플리케이션 컨테이너)

 

모든 노드 - 프락시, 네트워크 플로그인, kubelet(클러스터 구성)

마스터 노드 - API서버, 컨트롤러 매니저, 스케줄러, DNS 서버 

워커 노드 - 애플리케이션 

 

 

포드(Pod) : 컨테이너를 다루는 기본 단위

쿠버네티스의 기본 단위로 포드는 1개 이상의 컨테이너로 구성된 컨테이너의 집합 (스웜에서는 서비스 개념)

 

nginx-pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  containers:
  - name: my-nginx-container
    image: nginx:latest
    ports:
    - containerPort: 80
      protocol: TCP

 

일반적으로 쿠버네티스의 YAML파일

 

apiVersion: 오브젝트의 API버전
kind: 리소스의 종류 
metadata: 라벨, 주석, 이름 등과 같은 리소스의 부가 정보를 입력

spec: 리소스를 생성하기 위한 자세한 정보를 입력

 

이렇게 구성된다

# 작성한 YAML 파일 쿠버네티스에 생성
kubectl apply -f nginx-pod.yaml

# 특정 오브젝트 확인
kubectl get <오브젝트 이름>

# 생성된 리소스의 자세한 정보 
kubectl describe pods <포드이름>

하지만 아직 외부에서 접근할 수 있도록 노출된 상태는 아니다. 따라서 포드의 서버로 요청을 보내려면 포드 컨테이너 내부 IP로 접근해야한다. 

위에 있는 명령어를 사용해보자

kubectl describe pods <이름>

-> IP : ~ 나올 것이다 . 192.168.180.100

 

확인 된 IP를 클러스터의 노드 중 하나에 접속한 후 

curl 192.168.180.100 으로 켜보자. 

 

# 포드 컨테이너 내부로 직접 들어가기
kubectl exec -it <my-nginx-pod bash>
# 포드 log 확인 명령어
kubectl logs <포드 이름>

오브젝트 삭제

kubectl delete -f nginx-pod.yaml

 

새로운 우분투 컨테이너 추가해보자 

apiVersion: v1
kind: Pod
metadata:
  name: my-nginx-pod
spec:
  containers:
  - name: my-nginx-container
    image: nginx:latest
    ports:
    - containerPort: 80
      protocol: TCP

  - name: ubuntu-sidecar-container
    image: alicek106/rr-test:curl
    command: ["tail"]
    args: ["-f", "/dev/null"] # 포드가 종료되지 않도록 유지합니다

 

대시를 사용하는 항목은 여러 개의 항목을 정의할 수 있음을 의미한다. 

여기서 spec.containers의 하위 항목은 -name: 가 같이 대시로 구분이 되며, 여러 개의 컨테이너가 정의된 것이다.

# -c옵션으로 어떤 컨테이너에 대해 명령어를 수행할지 명시할 수 있다. 밑에는 배쉬 셸 실행
kubectl exec -it my-nginx-pod -c ubuntu-sidecar-container bash

 

이때 HTTP 요청을 전송하면 서버의 응답이 도착하는 것을 확인할 수 있다. 

우부투 컨테이너가 서버를 실행하고 있지 않아도 우분투 컨테이너 로컬 호스트에서 Nginx 로 접근이 가능하다. 

이는 포드 내의 컨테이너들이 네트워크 네임스페이스 등과 같은 리눅스 네임스페이스를 공유해 사용하기 때문

 

출처: https://coffeewhale.com/k8s/network/2019/04/19/k8s-network-01/

포드가 공유하는 리눅스 네임스페이스에 네트워크 환경만 있는 것은 아니다. 1개의 포드에 포함된 컨테이너들은 여러 개의 리눅스 네임스페이스를 공유한다. 그러나 당장은 자세히 알 필요 없으니 넘어 가겠다.

 

'하나의 포드는 하나의 완전한 애플리케이션' 

하지만 부가적인 기능이 필요할때는 주 컨테이너를 정하고, 기능 확장을 위한 추가 컨테이너를 함께 포드에 포함시킬 수 있다. 이렇게 정의된 부가적인 컨테이너를 사이드가 컨테이너라고 부르며, 사이드카 컨테이너는 포드 내의 다른 컨테이너와 네트워크 환경 등을 공유하게 된다. 

 

레플리카셋 (Replica Set) : 일정 개수의 포드를 유지하는 컨트롤러 

사용 이유 : 마이크로 서비스에서는 여러 개의 동일한 포드를 생성한 뒤 외부 요청이 각 포드에 적절히 분배 될 수 있어야 함. 이 때 동일한 여러 개의 포드를 생성하는 방법

1. 다른 이름을 가지는 여러 개의 포드를 직접 만듬 : 적절하지 못함

  1.  일일이 정의하는 것은 매우 비효율적
  2. 어떠한 이유로든지 삭제되거나, 포드가 위치한 노드에 장애가 발생해 더 이상 포드에 접근하지 못할 때 직접 포드를 삭제하고 다시 생성하지 않는 한 해당 포드는 다시 복귀되지 않음 

2. 리플리카셋(쿠버네티스 오브젝트)를 이용하는 것 

  1. 정해진 수의 동일한 포드가 항상 실행되도록 관리
  2. 노드 장애 등 이유로 포드를 사용할 수 없다면 다른 노드에서 포드를 다시 생성

 

사용하기 

replicaset-nginx.yaml

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.template ~: 포드를 생성할 때 사용할 템플릿을 정의, 즉 포드를 사용했던 내용을 동일하게 리플리카셋에도 정의

 

# 레플리카셋을 직접 생성해보자
kubectl apply -f replicaset-nginx.yaml

리플리카셋과 포드의 목록 확인 명령어

명령어를 사용할때 pods 대신 po로 replicasets 대신 rs로 사용할 수 있다. 

 

# 삭제 또한 동일하다
kubectl delete -f 
# 또는
kubectl delete rs
# 명령어를 사용하면 된다.

 

동작 원리

레플리카셋은 포드와 연결돼 있지 않다. 오히려 느슨한 연결을 유지하고 있으며, 이러한 느슨한 연결은 포드와 레플리카셋 정의 중 라벨 셀렉터를 이용해 이루어진다. 

 

라벨은 쿠버네티스 리소스의 부가적인 정보를 표현할 수 있을 뿐만 아니라, 서로 다른 오브젝트가 서로를 찾아야 할 때 사용되기도 한다. 

 

ex)레플리카셋은 spec.selector.matchLabel 에 정의된 라벨을 통해 생성해야 하는 포드를 찾는다.

즉, app:my-nginx-pods-label 라벨을 가지는 포드의 개수가 replicas 항목에 정의된 숫자인 3개와 일치하지 않으면 포드를 정의하는 포드 템플릿 항목의 내용으로 포드를 생성하는 것이다. 

이 말은 내가 수동으로 app:my-nginx-pods-label 라벨을 가지는 포드를 1개 만들었을 경우 2개만 만든다는 이야기다. 

또한 생성된 것중 하나의 포드 라벨을 삭제한다면 라벨을 가지는 포드가 2개가 됐으므로, 하나를 더 생성한다. 

 

그래서 레플리카셋과 포드의 라벨은 고유한 키-값 쌍이어야만 하는 것이다. 라벨을 레플리카셋에서 중요한 비중을 차지한다. 명심하자. 

 

이전 버전의 쿠버네티스에서는 레플리카셋이 아닌 레플리케시션 컨트롤러라는 오브젝트를 통해 포드의 개수를 유지했었다. 하지만 더 이상 사용하지 않으므로 설명하지 않겠다. 

 

디플로이먼트(Deploymetn) : 레플리카셋, 포드의 배포를 관리

레플리카셋만으로는 마이크로 서비스 구조의 컨테이너를 구성할 수 없다. 대부분은 리플리카셋과 포드의 정보를 정의하는 디폴로이먼트롤 YAML파일에 정의해 사용한다. 

 

디플로이먼트는 레플리카셋의 상위 오브젝트이다. 따라서 디플로이먼트를 사용하면 포드와 레플리카셋을 직접 생성할 필요가 없다. 

 

deployment-nginx.yaml

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

 

kind 항목이 Deployment로 바뀌었을 뿐 기존의 YAML과 달라진 것이 거의 없다.

# Deployment 생성
kubectl apply -f deployment-nginx.yaml

# Deployment 출력
kubectl get deployment

# Deployment 삭제
kubectl delete deploy my-nginx-deployment

생성 확인과 해시값

디플로이먼트로부터 생성된 레플리카셋과 포드는 특이한 해시값을 포함한 이름으로 생성됐다. 이 해시값은 포드를 정의하는 템플릿으로부터 생성된 것으로, 이 해시값에 대해서는 뒤에서 다시 설명할 '디플로이먼트를 사용하는 이유'에서 다루겠다. 

 

 

디플로이먼트를 사용하는 이유 
[핵심 이유]: 

애플리케이션의 업데이트와 배포를 더욱 편하게 만들기 위해서 

- 애플리케이션을 업데이트할 때 레플리카셋의 변경 사항을 저장하는 리버전을 남겨 롤백을 가능하게 해주고 무중단 서비스를 위해 포드의 롤링 업데이트의 전략을 지정할 수도 있다. 

 

# 생성할때 뒤에 record를 붙여보자 
kubectl apply -f deployment-nginx.yaml --record

그 후 포드의 이미지 버전을 nginx:1.11으로 변경하려면 다음과 같은 명령어를 입력하면 된다. 

kubectl set image my-nginx-deployment nginx=nginx:1.11 --record

그 후 레플리카셋의 목록을 출력해보면 이상하게 두 개의 레플리카셋이 있을 것이다. 즉 이전 버전의 리플리카셋을 삭제하지 않고 남겨두고 있는 것이다. 

--record=true : 변경 사항을 기록함으로 해당 버전의 이플리카셋을 보존 

--to-revision=[리버전 번호] : 리버전의 번호로 롤백함 

 

지금 어떠한 상태인지 알고 싶다면

1. kubectl get replicasets에 있는 DESIRED CURRENT READT를 보면 된다. 

2. kubectl describe deploy [라벨 이름]

 

서비스(Service): 포드를 연결하고 외부에 노출 

디플로이먼트를 통해 생성된 포드에 어떻게 접근할 수 있을지에 대해서 아직 알아보지 않았다. 

도커 컨테이너와 마찬가지로 포드의 IP는 영속적이지 않아 항상 변할 수 있단느 점도 유의해야 한다. 

 

쿠버네티스에서는 포드에 접근하도록 정의하는 방법이 도커와 다르다. 디플로이먼트를 생성할 때 포드를 외부에 노출하지 않으며, 디플로이먼트의 YAML 파일에는 단지 포드의 애플리케이션이 사용할 내부 포트만 정의한다.

 

YAML파일에서 containerPosr를 정의했다고 해서 이 포드가 바로 외부로 노출되는 것은 아니다. 이 때 별도로 서비스라고 부르는 쿠버네티스 오브젝트를 생성해야 한다. 

 

서비스에는 다양한 기능이 있지만 핵심 기능만 나열해보자면 아래와 같다.

- 여러 개의 포드에서 쉽게 접근할 수 있도록 고유한 도메인 이름을 부여한다.

- 여러 개의 포드에 접근할 때, 요청을 분산하는 로드 밸런서 기능을 수행한다.

- 클라우드 플랫폼의 로드 밸런서, 클러스터 노드의 포트 등을 통해 포드를 외부로 노출한다.

 

서비스의 종류

앞서 서비스의 개념과 사용 방법을 익히기 위해 포드와 서비스를 연결해보겠다.

deployment-hostname.yamldeployment-hostname.yaml

deployment-hostname.yaml

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: alicek106/rr-test:echo-hostname
        ports:
        - containerPort: 80

kubectl run -i --tty --rm debug --image=alicek106/ubuntu:curl --restart=Never curl 192.168.203.138 | grep Hello

 

포드에 접근할 수 있는 규칙을 정의하는 서비스 리소스를 새롭게 생성해 보자, 그 전에 한가지 알아야 할 점은 쿠버네티스의 서비스는 포드에 어떻게 접근할 것이냐에 따라 종류가 여러개로 세분화 돼있다. 

우리의 목적에 맞는 적절한 서비스를 선택해야한다.

 

- ClusterIP 타입: 쿠버네티스 내부에서만 포드들에 접근할 때 사용

- NodePort 타입: 포드에 접근할 수 있는 포트를 클러스터의 모든 노드에 동일하게 개방

- LoadBalancer 타입: 클라우드 플랫폼에서 제공하는 로드 밸런서를 동적으로 프로비저닝해 포드에 연결 

 

 

ClusterIP 타입

apiVersion: v1
kind: Service
metadata:
  name: hostname-svc-clusterip
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: ClusterIP

 

spec.selector : selector 항목은 이 서비스에서 어떠한 라벨을 가지는 포드에 접근할 수 있게 만들 것인지 결정 위에는 webserver이라는 라벨을 가지는 포드들의 집합에 접근할 수 있는 서비스를 생성 

 

spec.ports.port : 생성된 서비스는 쿠버네티스 내부에서만 사용할 수 있는 고유한 IP를 할당받음

 

spec.ports.targetPort : selector 항목에서 정의한 라벨에 의해 접근 대상이 된 포드들이 내부적을 사용하고 있는 포트를 입력함

 

spec.type : 이 서비스가 어떤 타입인지 나타냄 

 

# 서비스 생성
kubectl apply -f hostname-svc-clusterip.yaml 

# 생성된 서비스 목록 확인
kubectl get services
# services 대신 svc 가능

# 서비스 삭제 
kubectl delete svc 이름

 

서비스의 IP와 포트를 통해 포드에 접근할 수 있다. 게다가 서비스와 연결된 여러 개의 포드에 자동으로 요청이 분산된다. 즉 알아서 로드 밸런싱을 수행하는 것이다.

 

사비스에는 IP뿐만 아니라 서비스 이름 그 자체로도 접근 할 수 있다. 서비스나 포드를 쉽게 찾을 수 있도록 내부 DNS를 구동하고 있으며, 포드들은 자동으로 이 DNS를 사용하도록 설정되기 때문이다. 

 

 

 

NodePort 타입

 

hostname-svc-codeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: hostname-svc-nodeport
spec:
  ports:
    - name: web-port
      port: 8080
      targetPort: 80
  selector:
    app: webserver
  type: NodePort

 

생성은 위와 동일 

서비스 출력 화면을 보면 클러스터의 모든 노드에 내부 IP 또는 외부 IP를 통해 31136 포트로 접근하면 동일한 서비스에 연결할 수 있다. (31136포트를 통해 모든 노드에서 동일하게 접근)

 

단 AWS, GKE에서 쿠버네티스를 쓰고 있다면 방화벽 설정을 추가해야한다. 

# 규칙 추가
gcloud compute firewall-rules create temp-nodeport-svc --allow=tcp:포트번호

# 규칙 제거
gcloud compute firewall-rules delete temp-nodeport-svc

- 포트범위를 직접 지정하려면 API 서버의 다음의 옵션을 추가하거나 수정한다.

--service-node-port-range=30000-35000 

 

NodePort 타입의 서비스는 기본적으로 ClusterIP 기능을 포함하고 있다. 그렇기에 서비스의 내부 IP와 DNS 이름을 사용해 접근할 수 있다. 

 

LoadBalancer 타입

이 서비스는 서비스 생성과 동시에 로드 밸런서를 새롭게 생성해 포드와 연결한다. 

NodePort를 사용할 때는 각 노드의 IP를 알아야만 포드에 접근할 수 있었지만, LoadBalancer 타입의 서비스는 클라우드 플랫폼으로부터 도메인의 이름과 IP를 항당받기 때문에 더 쉽게 포드에 접근할 수 있다. 

 

단, 이 서비스는 로드 밸런서를 동적으로 생성하는 기능을 제공하는 환경에서만 사용할 수 있다는 점을 알아두자. 

AWS, GCP 등과 같은 클라우드 플랫폼 환경에서만 사용 가능하다. 

 

hostname-svc-lb.yaml

apiVersion: v1
kind: Service
metadata:
  name: hostname-svc-lb
spec:
  ports:
    - name: web-port
      port: 80
      targetPort: 80
  selector:
    app: webserver
  type: LoadBalancer

kubernetes.io/ko/docs/tutorials/stateless-application/expose-external-ip-address/

만약

pending 이라고 뜬다면 플랫폼 API를 잘 못 쓰고 있는 것이라고 생각된다. ( 내가 그랬다. )

 

추가로 돈을 내고 싶지는 않아서 진행하지 않았다.