본문 바로가기
프로그래밍

[kubernetes] 쿠버네티스 기본 용어 정리

by choihyuunmin 2024. 6. 19.

안녕하세요. 이번 포스팅에서는 쿠버네티스의 기본 용어와 명령어들을 한번 알아보겠습니다.
저는 3대의 가상 이미지를 띄워서 클러스터를 구성했습니다.
실습은 "한 권으로 배우는 도커&쿠버네티스" 책을 참고해서 진행했습니다.
 

1. 쿠버네티스 구성

쿠버네티스는 마스터노드와 워커노드로 구성되어 있습니다. 마스터노드는 클라이언트의 API 요청을 받고 워커 노드에 명령을 내리는 역할을 합니다. 명령을 받은 워커노드는 실제 컨테이너를 실행하는 역할을 하게 됩니다.
우선 설치된 쿠버네티스 클러스터의 정보부터 확인해보겠습니다.

> kubectl get nodes
NAME        STATUS     ROLES                  AGE    VERSION
ubun20-01   Ready      control-plane          154d   v1.28.5
ubun20-02   Ready      control-plane          154d   v1.28.5
ubun20-03   Ready      control-plane          154d   v1.28.5

 
node들의 정보는 위와 같습니다. ansible로 설치한 클러스터로, 설치되면 3개의 노드가 control-plane인 것을 확인할 수 있습니다.
kubectl cluster-info라는 명령어를 통해 클러스터의 정보도 가져올 수 있습니다.

> kubectl cluster-info
Kubernetes control plane is running at https://192.168.64.11:6443

 
저는 마스터노드를 11번 서버에 설정했기 때문에 cluster-info 명령어를 통해 11번 서버에서 control plane이 running 중이라는 상태를 확인할 수 있습니다.
 
참고로 마스터노드를 설정하는 방법은 로컬호스트에 저장된 kube config의 내용을 설정해주면 됩니다. 

vi ~/.kube/config

 
해당 파일의 clusters요소에 마스터노드의 같은 경로에 있는 파일의 내용을 넣어주면 됩니다. 서버의 ip는 마스터노드로 설정할 노드의 ip를 적어줍니다.

apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: 생략
  name: control-cluster

 
 
쿠버네티스 클러스터에서 실행중인 pod의 목록을 확인하려면, kubectl get pod 명령어를 실행합니다.
보다 자세한 결과를 확인하고 싶으면, -o wide 옵션을 사용합니다.
위에서 실행했던 get nodes 명령어에서도 -o wide 옵션을 사용하면 자세한 결과를 확인할 수 있습니다.

> kubectl get pod
NAME                                  READY   STATUS        RESTARTS       AGE
metallb-controller-665d96757f-dnnlz   1/1     Running       0              15s
metallb-speaker-l8spq                 4/4     Running       54 (12m ago)   60d
metallb-speaker-p5gxk                 4/4     Running       42 (12m ago)   60d
metallb-speaker-p5rgm                 4/4     Running       40 (12m ago)   60d
nginx-hello-846f4bdb6d-2wl86          1/1     Running       1 (12m ago)    2d18h
nginx-hello-846f4bdb6d-b8x6l          1/1     Running       0              15s
nginx-hello-846f4bdb6d-fz5kg          1/1     Running       1 (12m ago)    2d18h
nginx-hello-846f4bdb6d-hl7wc          1/1     Running       1 (12m ago)    2d18h
nginx-hello-846f4bdb6d-jb6jq          1/1     Running       0              15s
nginx-hello-846f4bdb6d-tg44q          1/1     Terminating   2 (12m ago)    2d18h
nginx-hello-846f4bdb6d-wqjfp          1/1     Terminating   2 (12m ago)    2d18h

> kubectl get pod -o wide
NAME                                  READY   STATUS        RESTARTS       AGE     IP               NODE        NOMINATED NODE   READINESS GATES
metallb-controller-665d96757f-dnnlz   1/1     Running       0              74s     10.233.109.152   ubun20-02   <none>           <none>
metallb-speaker-l8spq                 4/4     Running       54 (13m ago)   60d     192.168.64.12    ubun20-02   <none>           <none>
metallb-speaker-p5gxk                 4/4     Running       42 (13m ago)   60d     192.168.64.13    ubun20-03   <none>           <none>
metallb-speaker-p5rgm                 4/4     Running       40 (13m ago)   60d     192.168.64.11    ubun20-01   <none>           <none>
nginx-hello-846f4bdb6d-2wl86          1/1     Running       1 (13m ago)    2d18h   10.233.104.210   ubun20-01   <none>           <none>
nginx-hello-846f4bdb6d-b8x6l          1/1     Running       0              74s     10.233.109.153   ubun20-02   <none>           <none>
nginx-hello-846f4bdb6d-fz5kg          1/1     Running       1 (13m ago)    2d18h   10.233.104.209   ubun20-01   <none>           <none>
nginx-hello-846f4bdb6d-hl7wc          1/1     Running       1 (13m ago)    2d18h   10.233.104.213   ubun20-01   <none>           <none>
nginx-hello-846f4bdb6d-jb6jq          1/1     Running       0              74s     10.233.109.157   ubun20-02   <none>           <none>
nginx-hello-846f4bdb6d-tg44q          1/1     Terminating   2 (13m ago)    2d18h   10.233.70.34     ubun20-03   <none>           <none>
nginx-hello-846f4bdb6d-wqjfp          1/1     Terminating   2 (13m ago)    2d18h   10.233.70.31     ubun20-03   <none>           <none>

 
실행중인 pod를 삭제할때는 delete 명령을 실행합니다. kubectl delete {pod 이름}
 
pod를 실행하는 방법에는 명령어에는 run이 있습니다. run 명령어를 통해 hello-world라는 pod를 실행해보겠습니다.

> kubectl run hello-world --image=hello-world --restart=Always pod/hello-world created
pod/hello-world created
> kubectl get pod
NAME                    READY   STATUS             RESTARTS       AGE
hello-world             0/1     CrashLoopBackOff   1 (4s ago)     11s

 
이렇게 run 명령어로 pod를 실행할 수 있습니다. 생성된 pod는 delete 명령어로 삭제합니다. 다음으로는 manifest(매니페스트)를 통한 pod를 생성하는 방법이 있습니다. manifest는 yaml 확장자 파일을 이용해 pod를 생성하는 방법입니다. 일반 run 명령어보다 설정할 수있는 옵션이 많고, 한눈에 직관적으로 알아볼 수 있다는 장점이 있습니다.
nginx application을 생성하는 pod를 yaml형식의 manifest파일을 통해 생성해보겠습니다.
 
참고로 vi로 터미널에서 직접 수정할 수 있지만, 저는 vscode의 익스텐션을 통해 조금 더 간단하게 매니페스트를 작성합니다.

extension에서 yaml를 검색하고 위와 같은 익스텐션을 설치합니다.
 

 
설치가 됐으면 톱니바퀴를 클릭하고, Extension Settings(확장 설정)을 클릭합니다.
 

 
스크롤을 밑으로 내리다보면 나오는 Edit in settings.json 버튼을 클릭해줍니다.
 
settings.json에 아래와 같이 kubernetes 관련 설정을 추가해줍니다.

{
    "yaml.schemas": {
        "kubernetes": "*.yaml"
    }
}

 
만약 json파일 내에 다른 설정이 되어있다면 첫 depth에 위 구문을 넣어주면 됩니다.
설정이 완료됐다면 아래와 같은 자동완성 기능이 활성화되는 것을 볼 수 있습니다.

 
 
다시 돌아와서 매니페스트를 이용한 pod 생성에 대해 설명드리겠습니다.
 

apiVersion: v1
kind: Pod
metadata:
  name: nginx01
spec:
  containers:
  - name: nginx-test01
    image: nginx:latest

 
 
yaml파일을 통해 pod를 띄울때는 apply 명령어를 사용합니다.

> kubectl apply -f nginx-test.yml
pod/nginx01 created
> kubectl get pod
NAME                    READY   STATUS    RESTARTS       AGE
nginx01                 1/1     Running   0              10s

 
yaml 파일을 통해 생성한 pod는 생성할 때와 마찬가지로 f 옵션을 준뒤 delete 명령어를 통해 삭제합니다.

> kubectl delete -f nginx-test.yml
pod "nginx01" deleted

 

2. 디플로이먼트

디플로이먼트(deployment)는 파드의 개수를 관리합니다. 파드는 레플리카셋이라는 수치를 통하여 파드의 개수를 설정하고 유지합니다. 디플로이먼트는 이런 파드의 레플리카셋을 관리하고 스펙을 정의하는 기능을 합니다. 또한 생성되고 제거되는 pod의 이력을 관리하는 기능을 가지고 있어, 단순히 pod를 생성하고 레플리카셋을 설정하는 것보다 디플로이먼트를 통해 pod를 관리합니다.
 
디플로이먼트도 kubectl 명령어를 통해 생성할 수 있습니다. pod는 run 명령어를 통해 생성했다면, 디플로이먼트는 create 명령어를 통해 생성합니다.
 

> kubectl create deployment deploy-hello --image=hello-world
deployment.apps/deploy-hello created

 
생성된 디플로이먼트는 아래의 명령어들로 확인할 수 있습니다.
 

> kubectl get pod
NAME                            READY   STATUS      RESTARTS      AGE
deploy-hello-7c478bcd59-fsc8p   0/1     Completed   3 (33s ago)   57s
> kubectl get replicaset
NAME                      DESIRED   CURRENT   READY   AGE
deploy-hello-7c478bcd59   1         1         0       2m6s
> kubectl get deployment
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
deploy-hello   0/1     1            0           2m20s

 
생성된 디플로이먼트의 삭제는 pod와 마찬가지로 delete 명령어를 통해 수행합니다.

> kubectl delete deployment deploy-hello
deployment.apps "deploy-hello" deleted

 
다음으로 디플로이먼트를 실행할 때, 레플리카셋을 조정하는 방법을 알아보겠습니다.
레플리카셋이란, pod의 개수를 원하는 개수만큼 유지시켜 줄 때의 개수를 말합니다. 예를 들어 레플리카셋을 3개로 지정하면 쿠버네티스 클러스터는 해당 디플로이먼트를 통해 생성된 pod의 개수를 항상 3개로 유지시키려고 합니다.
 
nginx이미지를 통해 레플리카셋이 3인 pod를 생성해보겠습니다.

> kubectl create deployment deploy-nginx --image=nginx --replicas=3
deployment.apps/deploy-nginx created

 
생성된 pod를 확인해보면, READY 컬럼에 3개의 pod가 관리되고 있음을 알 수 있습니다.
 

> kubectl get pod
NAME                            READY   STATUS     RESTARTS   AGE
deploy-nginx-7f979874cf-7qp6w   1/1     Running    0          51s
deploy-nginx-7f979874cf-s7lnt   1/1     Running    0          51s
deploy-nginx-7f979874cf-tdj7w   1/1     Running    0          51s

 
디플로이먼트와 레플리카셋도 확인해보면 3개의 pod를 관리하고 있음을 확인할 수 있습니다.

> kubectl get deployment,replicaset
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/deploy-nginx   3/3     3            3           99s

NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/deploy-nginx-7f979874cf   3         3         3       99s

 
레플리카셋이 3개로 설정된 pod를 자세히 살펴보겠습니다. -o wide 옵션을 통해 pod의 상태를 더 자세히 볼 수 있습니다.
 

> kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
deploy-nginx-7f979874cf-qwhs2   1/1     Running   0          30s   10.233.70.41     ubun20-03   <none>           <none>
deploy-nginx-7f979874cf-skn2j   1/1     Running   0          30s   10.233.70.39     ubun20-03   <none>           <none>
deploy-nginx-7f979874cf-vc4mv   1/1     Running   0          30s   10.233.104.225   ubun20-01   <none>           <none>

 
3개의 pod는 각각 10.233.70.41, 10.233.70.39, 10.233.104.225의 IP로 실행됐으며, 위에 두개는 3번 노드에, 아래 한개는 1번 노드에 실행되고 있음을 확인할 수 있습니다. 해당 IP로 띄워진 pod는 클러스터 내부에서만 통신이 가능하고, 현재는 pod를 외부로 노출시키는 작업을 하지 않았기 때문에 로컬호스트에서 해당 IP주소로 접속해보면 접속이 되지 않는 것을 확인할 수 있습니다. 
쿠버네티스 클러스터의 서버에 접속해서 nc 명령어를 통해 네트워크 통신을 확인해보면 정상적으로 통신이 되는 것을 확인할 수 있습니다.

> nc -zv 10.233.70.39 80
Connection to 10.233.70.39 80 port [tcp/http] succeeded!

 
여기서 생성된 pod 중 하나를 삭제해보겠습니다.

> kubectl delete pod deploy-nginx-7f979874cf-qwhs2
pod "deploy-nginx-7f979874cf-qwhs2" deleted

 
그다음 다시 pod를 확인해보면 아래와 같은 결과를 확인할 수 있습니다.

> kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE     IP               NODE        NOMINATED NODE   READINESS GATES
deploy-nginx-7f979874cf-jcdd5   1/1     Running   0          7s      10.233.70.42     ubun20-03   <none>           <none>
deploy-nginx-7f979874cf-skn2j   1/1     Running   0          4m27s   10.233.70.39     ubun20-03   <none>           <none>
deploy-nginx-7f979874cf-vc4mv   1/1     Running   0          4m27s   10.233.104.225   ubun20-01   <none>           <none>

 
위에서 제거한 qwhs2의 id를 가진 pod가 사라지고 새로운 pod가 생겼습니다.(AGE가 7초) 이렇듯 replicaset이 설정된 deployment는 pod가 어떤 요인에 의해 삭제되면 다른 pod를 생성하여 개수를 유지하려는 성질이 있습니다.
 
이제 명령어를 통해 생성한 디플로이먼트를 삭제하고 매니페스트를 통해 새로운 디플로이먼트를 생성해보겠습니다.

> kubectl delete deployment deploy-nginx
deployment.apps "deploy-nginx" deleted

> kubectl get deployment
No resources found in default namespace.

 

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deploy-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: nginx-deploy
  template:
    metadata:
      labels:
        app.kubernetes.io/name: nginx-deploy
    spec:
      containers:
      - name: nginx
        image: nginx:latest

 
위 구문에서 처음 나오는 spec은 디플로이먼트에 대한 상태를 정의합니다. 그 안에 있는 또다른 spec은 pod의 상태를 정의하는 것으로 여기서 유의할 점은 디플로이먼트의 matchLabels와 pod의 labels의 이름을 같도록 지정해주어야 합니다.
 
이제 생성한 매니페스트를 통해 디플로이먼트를 실행시켜보겠습니다.

> kubectl apply -f nginx-deploy.yaml
deployment.apps/deploy-nginx created

 
생성된 디플로이먼트를 확인해보겠습니다.

> kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
deploy-nginx-5b6549999b-m2f69   1/1     Running   0          5s    10.233.109.166   ubun20-02   <none>           <none>
deploy-nginx-5b6549999b-qmvhp   1/1     Running   0          5s    10.233.104.223   ubun20-01   <none>           <none>
deploy-nginx-5b6549999b-zkpld   1/1     Running   0          5s    10.233.70.45     ubun20-03   <none>           <none>

 
레플리카셋이 3개인 디플로이먼트가 잘 실행된 것을 확인할 수 있습니다. 위에서 생성한 yaml파일을 수정하면서 레플리카셋과 이미지의 버전 등을 변경해줄 수 있습니다. metadata의 이름을 같게 설정하면 기존에 생성된 디플로이먼트의 상태를 업데이트 해주는 용도로 사용할 수 있습니다.
 
 

3. 서비스

다음으로 서비스에 대해 알아보겠습니다. 서비스는 pod의 외부 접근을 위한 기능을 수행합니다. pod와 디플로이먼트 절에서 확인했듯이 IP주소는 pod가 재생성되면 재할당되는 특성이 있습니다. 이때, 클라이언트는 pod에 접속하기 위해 갖고 있던 IP주소가 변경되었기 때문에 pod로 요청이 정상적으로 이루어지지 않을 수 있습니다. 이런 문제를 해결하기 위해 서비스를 사용합니다. 서비스는 클라이언트의 요청을 받아서 pod로 포워딩해주는 역할을 합니다.
서비스는 이런 특성을 활용해서 외부 트래픽 노출, 로드밸런싱, 서비스에 대한 디스커버리 등의 기능을 수행합니다.
 

           +-----------------------+
           |        Client         |
           +-----------+-----------+
                       |
                       v
           +-----------+-----------+
           |        Service        |
           +-----------+-----------+
                       |
         +-------------+--------------+
         |             |              |
         v             v              v
     +---+---+     +---+---+      +---+---+
     |  Pod  |     |  Pod  |      |  Pod  |
     |(App 1)|     |(App 2)|      |(App 3)|
     +---+---+     +---+---+      +---+---+

 
쿠버네티스 서비스의 종류에는 ClusterIP, NodePort, LoadBalancer, ExternalName이 있습니다. 서비스의 종류는 매니페스트에서 type으로 지정할 수 있습니다. 이제 서비스 종류 각각에 대해 알아보겠습니다.
 

(1) ClusterIp

cluster ip는 서비스의 default 값으로, 클러스터 내에서만 pod에 접근할 수 있도록 합니다.
cluster ip를 설정하면, 클러스터 내에서만 접근 가능한 IP를 할당하고, 외부에서는 접근할 수 없습니다.
cluster ip를 설정하는 매니페스트를 작성해보겠습니다.
 
서비스 실습은 위에서 작성했던 디플로이먼트의 web-deploy먼트를 활용해서 진행하겠습니다.

apiVersion: v1
kind: Service
metadata:
  name: web-service
spec:
  selector:
    app.kubernetes.io/name: nginx-deploy
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 80

 
디플로이먼트의 매니페스트에서 지정한 web-deploy를 selector에 지정해주고, type에 ClusterIP를 넣습니다.
외부 포트는 80번으로 지정해줍니다.
서비스를 작성할 때는 연동할 디플로이먼트를 spec.selector.app.kubernetes.io/name을 통해 지정해주어야 합니다.
디플로이먼트와 같이 실행 명령어는 kubectl apply 입니다. 명령어를 통해서 서비스를 실행시켜 보겠습니다.

> kubectl apply -f service-clusterip.yaml
service/web-service created

 
서비스를 실행시켰으면 , 정상적으로 기동됐는지 확인해보겠습니다.
 

> kubectl get svc
NAME          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes    ClusterIP   10.233.0.1     <none>        443/TCP   30h
web-service   ClusterIP   10.233.34.110   <none>        80/TCP    2s

 
10.233.34.110의 IP로 cluster ip가 생성된 것을 확인할 수 있습니다.
하지만, cluster ip는 클러스터 내부에서만 통신이 되기 때문에, 제 로컬호스트에서는 접속이 되지 않습니다. 통신이 정상적으로 수행되는 지 확인하기 위해 임시 pod를 하나 생성해서 curl 명령어를 수행해보겠습니다.

> kubectl run test-cluster --image=nginx
kubectl run test-cluster --image=nginx
> kubectl exec -it test-cluster -- /bin/bash
root@test-cluster:/#
root@test-cluster:/# curl "10.233.34.110:80"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

 
 
curl을 통해 명령어를 전송하면, 위와 같이 정상적으로 nginx페이지가 반환되는 것을 확인할 수 있습니다.
계속해서 다른 서비스들을 실습하기 위해 띄워놓은 서비스와 디플로이먼트를 종료하겠습니다.
 

> kubectl delete pod test-cluser
pod "test-cluster" deleted
> kubectl delete -f service-clusterip.yaml
service "web-service" deleted
> kubectl delete -f nginx-deploy.yaml
deployment.apps "deploy-nginx" deleted

 

(2) NodePort

NodePort는 각 노드의 특정 포트를 통해 외부 접근을 제공합니다. NodeIp:NodePort를 통해 클러스터 외부에서 클러스터 내부의 pod에 접근할 수 있도록 해줍니다. NAT와 유사한 서비스를 제공합니다.

        요청
         |
         v
+-------------------+
|       Node        |
|  +-------------+  |
|  |  NodePort   |  |  <- 클러스터의 모든 노드에서 특정 포트가 열림
|  +-------------+  |
|         |         |
|         v         |
|  +-------------+  |
|  |     Pod     |  |  <- 서비스가 포드로 트래픽을 라우팅
|  |  (Container)|  |
|  +-------------+  |
+-------------------+

 
 
이제 NodePort를 위한 매니페스트를 작성해보겠습니다.

apiVersion: v1
kind: Service
metadata:
  name: web-service-nodeport
spec:
  selector:
    app.kubernetes.io/name: nginx-deploy
  type: NodePort
  ports:
    - protocol: TCP
      nodePort: 31001
      port: 80
      targetPort: 80

 
ClusterIp와 마찬가지로 selector에는 생성된 디플로이먼트의 name을 작성해줍니다.
type은 NodePort로 작성하고, NodePort에서는 ports 요소에 nodePort라는 아이템을 작성해줍니다. nodePort는 외부에서 접근하는 포트를 의미합니다. 외부에서 31001이라는 포트로 접근하면 서비스의 80포트로 접속되고, 서비스의 80포트가 pod의 타겟 포트인 80 포트로 다시 전달한다는 의미입니다.
 
다시 디플로이먼트를 실행시키고 이번에 생성한 NodePort의 매니페스트를 실행시켜 접속이 가능한 지 확인해보겠습니다.
 

> kubectl apply -f nginx-deploy.yaml
deployment.apps/deploy-nginx created
> kubectl apply -f service-nodeport.yaml
service/web-service-nodeport created
> kubectl get svc
NAME                   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes             ClusterIP   10.233.0.1     <none>        443/TCP        31h
web-service-nodeport   NodePort    10.233.42.69   <none>        80:31001/TCP   35s

 
서비스를 확인하면 80:31001과 같이 외부로 노출된 포트가 뒤쪽에 나오는 것을 확인할 수 있습니다.
제 클러스터의 pod를 확인해보면

> kubectl get pod -o wide
NAME                            READY   STATUS    RESTARTS   AGE    IP               NODE        NOMINATED NODE   READINESS GATES
deploy-nginx-5b6549999b-7mqbs   1/1     Running   0          106s   10.233.70.51     ubun20-03   <none>           <none>
deploy-nginx-5b6549999b-gqgcv   1/1     Running   0          106s   10.233.70.48     ubun20-03   <none>           <none>
deploy-nginx-5b6549999b-th7bk   1/1     Running   0          106s   10.233.104.234   ubun20-01   <none>           <none>

 
3번 노드에 두개의 pod가 실행되고 있음을 확인할 수 있고, 3번 노드의 IP에 31001 포트로 접근해보면

 
이렇게 nginx로 접속이 되는 것을 확인할 수 있습니다.
 
 

(3) LoadBalancer

다음으로는 로드밸런서에 대해 알아보겠습니다.
로드밸런서는 서비스에서 NodePort의 상위호환이라고 생각하면 됩니다. 외부 IP와 포트를 지정해놓으면 이 IP와 포트를 따라 NodePort로 들어오고 NodePort에서 Pod로 분배되는 순으로 접속하게 됩니다.
다시 매니페스트를 통해서 로드밸런서가 지정된 서비스를 실행시켜보겠습니다.
 

apiVersion: v1
kind: Service
metadata:
  name: web-service-loadbalancer
spec:
  selector:
    app.kubernetes.io/name: nginx-deploy
  type: LoadBalancer
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  externalIPs:
    - 192.168.64.30

 
매니페스트를 통해 서비스를  생성했습니다. port는 서비스가 사용할 포트, targetPort는 pod가 사용할 포트입니다. 서비스의 80포트로 접속하면 pod의 80번 포트를 통해 디플로이먼트에 접속이 되는 구조입니다.
실행된 서비스의 정보를 확인해보겠습니다.
 

> kubectl get svc
NAME                       TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)        AGE
kubernetes                 ClusterIP      10.233.0.1      <none>          443/TCP        3d5h
web-service-loadbalancer   LoadBalancer   10.233.44.171   192.168.64.30   80:30825/TCP   81s

 
서비스가 위에서 설정한 192.168.64.30이라는 IP와 80라는 외부 포트를 통해 네트워크가 오픈된 것을 볼 수 있습니다. 80포트로 접속하면 nodeport로 설정된 30825 포트로 접속되는 구조입니다. 접속해보기 전에, 새로 생긴 EXTERNAL-IP는 제 로컬호스트에서 통신이 되고 있지 않은 상황이니, 포트포워딩을 따로 설정해주겠습니다. 8080 포트를 30번 서버의 80포트에 매핑시켜주겠습니다.
 

> kubectl port-forward svc/web-service-loadbalancer 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80

 

 
8080포트로 접속하면 nginx까지 접속이 되는 것을 확인할 수 있습니다. 이렇게 LoadBalance를 사용하면 하나의 IP를 통해 쿠버네티스 클러스터의 pod에 접속할 수 있습니다.

 

4. 스토리지 볼륨

스토리지는 도커의 볼륨과 비슷한 역할을 합니다. 쿠버네티스 클러스터의 컨테이너 파일을 보존하기 위해 스토리지 볼륨을 사용합니다.
쿠버네티스의 pod가 삭제된 후 다시 실행하게 되면 기존 컨테이너 내부에 존재하는 파일은 모두 사라집니다.  이러한 문제점을 해결하기 위해 스토리지 볼륨을 사용하는데, 스토리지는 pod처럼 노드 내부에 디스크 공간을 공유하거나 외부 스토리지 시스템과 연결하는 방식이 있습니다.
 
스토리지에는 emptyDir, hostPath, PersistentVolume의 세가지 종류가 있습니다.
 

(1) emptyDir

emptyDir는 pod 내부에서 임시적으로 사용하는 볼륨입니다. 따라서 노드의 디스크를 일시적으로 사용합니다, emptyDir 볼륨은 파드가 노드에 할당될 때 처음으로 생성되며, 노드가 실행하는 동안 존재합니다.
emptyDir는 pod에 할당되어 있는 스토리지 성격을 가지고 있어 pod 내부의 서로 다른 컨테이너에서는 데이터 공유가 가능하지만 다른 pod에서는 접근할 수 없습니다.
 
emptyDir를 생성하기 위한 매니페스트를 작성해보겠습니다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-volume-1
spec:
  containers:
    - name: nginx-deploy
      image: nginx:latest
      volumeMounts:
        - mountPath: /mount1
          name: emptydir-1
  volumes:
    - name: emptydir-1
    emptyDir: {}

 
container를 생성할 때 volumeMounts를 이용해서 마운트 정보를 설정합니다. volumeMounts에 설정하는 name은 밑에 작성할 volumes와 같은 이름으로 지정해주어야합니다. volumes 내의 emptyDir은 볼륨의 타입을 의미합니다. {}는 추가 옵션을 사용하지 않겠다는 의미입니다.
 
pod를 실행시키겠습니다.

> kubectl apply -f volume-emptydir.yaml
pod/nginx-volume-1 created
> kubectl get pod -o wide
NAME                            READY   STATUS        RESTARTS        AGE   IP               NODE        NOMINATED NODE   READINESS GATES
deploy-nginx-5b6549999b-7mqbs   1/1     Terminating   3 (6d23h ago)   8d    10.233.70.59     ubun20-03   <none>           <none>
deploy-nginx-5b6549999b-gqgcv   1/1     Terminating   3 (6d23h ago)   8d    10.233.70.60     ubun20-03   <none>           <none>
nginx-volume-1                  1/1     Running       0               46s   10.233.104.193   ubun20-01   <none>           <none>

 
pod를 실행시킨 후 확인을 해보면, volume이 생긴 것을 볼 수 있습니다.
이제 exec 명령어를 통해 pod 내부로 들어가 /mount1 디렉터리가 있는지 확인해보겠습니다.
 

> kubectl exec -it nginx-volume-1 -- /bin/bash
root@nginx-volume-1:/# ls
bin  boot  dev	docker-entrypoint.d  docker-entrypoint.sh  etc	home  lib  media  mnt  mount1  opt  proc  root	run  sbin  srv	sys  tmp  usr  var
root@nginx-volume-1:/# cd /mount1
root@nginx-volume-1:/mount1# touch 1.txt
root@nginx-volume-1:/mount1# ls
1.txt

 
매니페스트 파일에서 지정한 경로가 생성된 것을 확인할 수 있으며, 디렉터리 내부에 들어가 파일까지 생성이 되는 것을 확인할 수 있습니다. emptyDir는 pod 내부에서 해당 볼륨을 바라보고 deploy에서 해당 볼륨을 마운트시키는 방식으로 사용합니다.
 
 

(2) hostPath

hostPath는 호스트 노드의 파일 시스템에 저장소를 마운트하는 방식입니다. 호스트 노드에 파일 시스템이 생기기 때문에 서로 다른 pod라고 할 지라도 같은 노드에서 실행되면 같은 파일 시스템을 바라보게 됩니다.
서로 다른 Pod사이에 데이터를 공유할 수 있다는 장점이 있지만, 반대로 해당 노드에 장애가 생기면 다른 pod들에게도 데이터 유실의 단점도 존재합니다.
 
하지만 보안 위협이 있어 사용이 권고되고 있지는 않습니다.
https://kubernetes.io/ko/docs/concepts/storage/volumes/#hostpath

HostPath 볼륨에는 많은 보안 위험이 있으며, 가능하면 HostPath를 사용하지 않는 것이 좋다. HostPath 볼륨을 사용해야 하는 경우, 필요한 파일 또는 디렉터리로만 범위를 지정하고 ReadOnly로 마운트해야 한다.AdmissionPolicy를 사용하여 특정 디렉터리로의 HostPath 액세스를 제한하는 경우, readOnly 마운트를 사용하는 정책이 유효하려면 volumeMounts 가 반드시 지정되어야 한다.

 
 

(3) PV(Persistent Volume)

PV는 외부 스토리지를 의미합니다. PV는 PVC(Persistent Volume Claim)을 이용해서 사용하는데, PVC는 스토리지를 동적으로 바인딩하기 위한 객체를 의미합니다. 쿠버네티스 사용자는 PVC를 통해 외부 스토리지인 PV를 요청하는 식으로 볼륨을 사용합니다.
PV에는 nfs라는 스토리지를 사용하는데, nfs는 network file system의 약자로 네트워크로 연결되는 스토리지 역할을 합니다.
저는 클러스터의 3번 노드에 nfs 서버를 설치하고 나머지 2대의 노드에는 클라이언트를 설치하겠습니다.
 
1) 1번 노드

> ssh ubun20-01
Last login: Wed Jul  3 12:09:24 2024
(base) choi@ubun20-01:~$ sudo apt install nfs-common

 
2) 2번 노드

(base) choi@ubun20-01:~$ ssh ubun20-02
Last login: Sat Jun 22 12:06:48 2024
choi@ubun20-02:~$ sudo apt install nfs-common

 
3) 3번 노드

choi@ubun20-02:~$ ssh ubun20-03
Last login: Sat Jun 22 12:06:41 2024
choi@ubun20-03:~$ sudo apt install nfs-common
choi@ubun20-03:~$ sudo apt install nfs-kernel-server
choi@ubun20-03:~$ systemctl status nfs-server
● nfs-server.service - NFS server and services
     Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
     Active: active (exited) since Wed 2024-07-03 12:33:38 UTC; 19s ago
   Main PID: 4439 (code=exited, status=0/SUCCESS)
      Tasks: 0 (limit: 9379)
     Memory: 0B
     CGroup: /system.slice/nfs-server.service

Jul 03 12:33:36 ubun20-03 systemd[1]: Starting NFS server and services...
Jul 03 12:33:38 ubun20-03 systemd[1]: Finished NFS server and services.

 
클라이언트와 서버 설치를 모두 마쳤습니다.
이제는 공용 볼륨으로 사용할 디렉터리를 nfs server인 3번 노드에 생성하겠습니다.
 

choi@ubun20-03:~$ sudo -i
root@ubun20-03:~# cd /tmp/
root@ubun20-03:/tmp# mkdir k8s-pv

 
root계정으로 전환해서 /tmp 아래에 k8s-pv라는 디렉터리를 만들었습니다. 이 디렉터리를 제 쿠버네티스 클러스터의 PV로 사용하고자 합니다. 설정 파일을 통해 공유 디렉터리를 지정해줍니다.
 

root@ubun20-03:/tmp# vi /etc/exports
/tmp/k8s-pv 192.168.64.12(rw,no_root_squash)
root@ubun20-03:/tmp# systemctl restart nfs-server
root@ubun20-03:/tmp# systemctl status nfs-server
● nfs-server.service - NFS server and services
     Loaded: loaded (/lib/systemd/system/nfs-server.service; enabled; vendor preset: enabled)
     Active: active (exited) since Wed 2024-07-03 12:40:26 UTC; 29s ago
    Process: 9393 ExecStartPre=/usr/sbin/exportfs -r (code=exited, status=0/SUCCESS)
    Process: 9394 ExecStart=/usr/sbin/rpc.nfsd $RPCNFSDARGS (code=exited, status=0/SUCCESS)
   Main PID: 9394 (code=exited, status=0/SUCCESS)

Jul 03 12:40:25 ubun20-03 systemd[1]: Starting NFS server and services...
Jul 03 12:40:25 ubun20-03 exportfs[9393]: exportfs: /etc/exports [2]: Neither 'subtree_check' or 'no_subtree_check' specified for export "192.168.64.12:/tmp/k8s-pv".
Jul 03 12:40:25 ubun20-03 exportfs[9393]:   Assuming default behaviour ('no_subtree_check').
Jul 03 12:40:25 ubun20-03 exportfs[9393]:   NOTE: this default has changed since nfs-utils version 1.0.x
Jul 03 12:40:26 ubun20-03 systemd[1]: Finished NFS server and services.

 
/etc/exports를 vi로 열어서 공유할디렉터리:허용할IP의 형식으로 한 줄을 추가해줍니다.
이 때, 괄호 안의 rw는 읽기와 쓰기, no_root_squash는 클라이언트의 루트 권한을 인정해준다는 의미입니다.
만약 no_root_squash 옵션이 없다면 클라이언트가 해당 디렉터리에 파일을 쓸 수 있는 권한이 부여되지 않습니다.
파일을 저장한 뒤 nfs server를 재시작해줍니다.
 
이제 PV 사용을 위한 매니페스트를 작성해보겠습니다.

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-1
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 100Mi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: pv-test-1
  nfs:
    server: 192.168.64.13
    path: /tmp/k8s-pv

 
종류는 PersistentVolume, spec의 accessModes는 ReadWriteOnce로 지정합니다.
스토리지 용량은 100M, persistentVolumeReclaimPolicy를 Retain으로 설정하는 데, 여기서 Retain은 PVC가 삭제되어도 PV 내부의 데이터는 유지해주는 옵션입니다.
reclaim에는 retain외에도 Delete, Recycle이 있습니다. Delete는 PVC가 삭제되면 연계되어 있는 외부 스토리지도 삭제해주는 옵션입니다.
 
다음으로 PVC의 매니페스트 파일을 작성하겠습니다.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30Mi
  storageClassName: pv-test-1

 
kind는 PersistentVolumeClaim으로, storageClassName을 앞서 작성한 PV의 className과 같게 작성합니다.
 
마지막으로 실습을 위한 pod 매니페스트를 작성하겠습니다.

apiVersion: v1
kind: Pod
metadata:
  name: nginx-volume
spec:
  nodeSelector:
    kubernetes.io/hostname: ubun20-02
  containers:
    - name: nginx-volume
      image: nginx:latest
      volumeMounts:
        - name: nfs-pv-1
          mountPath: /mount01
  volumes:
    - name: nfs-pv-1
      persistentVolumeClaim:
        claimName: pvc-1

 
여기서 주의할 점은 nfs server의 설정 파일에서 지정한 2번 노드에서만 접근이 되고 있기 때문에 nodeSelector를 통해 생성될 노드를 2번 노드로 지정해주어야합니다. 그리고 volumeMounts의 name과 아래 volume 설정의 name을 같게 해주어야하며, volume 설정에서의 persistentVolumeClaim의 claimName은 앞서 pvc에서 설정한 이름과 같도록 맞추어주어야 합니다.
 
이제 볼륨을 실행해 보겠습니다.

> kubectl apply -f volume-PV.yaml
persistentvolume/pv-1 created

 
PV를 확인하는 명령어는 kubectl get pv 입니다. 생성된 pv의 상태를 확인해보겠습니다.

> kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv-1   100Mi      RWO            Retain           Available           pv-test-1               33s

 
매니페스트에서 지정한 내용이 그대로 표시되는 것을 확인할 수 있습니다.
계속해서 PVC도 띄우겠습니다.

> kubectl apply -f volume-PVC.yaml
persistentvolumeclaim/pvc-1 created

 
PVC도 PV와 비슷하게 kubectl get pvc 명령어로 상태를 확인할 수 있습니다.
 

> kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-1   Bound    pv-1     100Mi      RWO            pv-test-1      61s

 
pv-1과 연결되어 있는 것을 확인할 수 있습니다. pvc를 생성했으니 다시 pv를 확인해보겠습니다.
 

> kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM           STORAGECLASS   REASON   AGE
pv-1   100Mi      RWO            Retain           Bound    default/pvc-1   pv-test-1               4m4s

 
처음 확인했을 때 STATUS는 Available이었는데, 다시 확인해보니 Bound 상태로 변경된 것을 볼 수 있습니다. CLAIM도 기존에는 공백이었지만 pvc-1로 지정된 것을 볼 수 있습니다. 연결이 잘 되었다는 의미입니다.
마지막으로 pod를 생성해보겠습니다.
 

> kubectl apply -f volume-pod.yaml
pod/nginx-volume created
> kubectl get pod -o wide
NAME           READY   STATUS    RESTARTS   AGE   IP               NODE        NOMINATED NODE   READINESS GATES
nginx-volume   1/1     Running   0          16s   10.233.109.184   ubun20-02   <none>           <none>

 
pod가 2번 노드에 정상적으로 생성된 것을 확인할 수 있습니다. 이제 pod의 내부에 들어가서 마운트된 볼륨의 경로에 파일을 생성해보겠습니다.
 

> kubectl exec -it nginx-volume -- /bin/bash
root@nginx-volume:/# ls
bin  boot  dev	docker-entrypoint.d  docker-entrypoint.sh  etc	home  lib  media  mnt  mount01	opt  proc  root  run  sbin  srv  sys  tmp  usr	var
root@nginx-volume:/# cd /mount01
root@nginx-volume:/mount01# ls
root@nginx-volume:/mount01# echo "hello volume" > volume-test.txt
root@nginx-volume:/mount01# ls
volume-test.txt
root@nginx-volume:/mount01# cat volume-test.txt
hello volume
root@nginx-volume:/mount01#

 
텍스트 파일을 하나 생성했습니다. 이제 공용 볼륨으로 이동해서 파일이 정상적으로 생성됐는지 확인해보겠습니다.
위에서 생성한 3번 노드의 k8s-pv 디렉터리로 이동하겠습니다.
 

> ssh ubun20-03
Last login: Wed Jul  3 13:17:35 2024 from 192.168.64.1
choi@ubun20-03:~$ cd /tmp/k8s-pv
choi@ubun20-03:/tmp/k8s-pv$ ls
volume-test.txt
choi@ubun20-03:/tmp/k8s-pv$ cat volume-test.txt
hello volume

 
정상적으로 생성된 것을 확인할 수 있습니다. 도커의 volume과 유사한 동작을 수행합니다.
모든 테스트가 끝났으니 생성의 역순으로 PV 및 pod를 제거합니다. 실습 후에 바로 제거하지 않으면 나중에 확인했을 때 어느 파일을 통해 서비스를 생성했는 지 체크가 바로 되지 않기 때문에 바로 제거하는 습관을 가져야합니다.
 

> k delete -f volume-pod.yaml
pod "nginx-volume" deleted

> k delete -f volume-PVC.yaml
persistentvolumeclaim "pvc-1" deleted

> k delete -f volume-PV.yaml
persistentvolume "pv-1" deleted

 
이렇게 간단한 쿠버네티스의 기능들을 알아보는 시간을 가졌습니다.
다음 포스팅에서는 스테이트풀셋, 인그레스, 헬름, job에 대해 알아보겠습니다.